Bnava13 commited on
Commit
47b2831
·
verified ·
1 Parent(s): 13811b9

NewRecommenderDataset

Browse files
Files changed (1) hide show
  1. app.py +113 -561
app.py CHANGED
@@ -1,597 +1,149 @@
1
- import gradio as gr
2
  import pandas as pd
3
- import numpy as np
4
  import difflib
5
- import plotly.graph_objects as go
6
  from sklearn.feature_extraction.text import TfidfVectorizer
7
  from sklearn.metrics.pairwise import cosine_similarity
8
- from sklearn.preprocessing import MinMaxScaler
 
9
 
10
- # Load dataset with proper error handling
11
- def load_data(file_path='games_march2025_cleaned.csv', max_rows=88899):
12
  try:
13
- data = pd.read_csv(file_path, quotechar='"', on_bad_lines='skip', nrows=max_rows, engine='python')
14
- print(f"Successfully loaded {len(data)} games from {file_path}")
15
  return data
16
  except Exception as e:
17
  print(f"Error loading data: {e}")
18
- # Return empty DataFrame with expected columns to avoid crashing
19
- return pd.DataFrame(columns=['name', 'genres', 'categories', 'tags', 'platforms', 'positive_ratings', 'price'])
20
-
21
- # Load and preprocess data
22
- data = load_data()
23
-
24
- # Only proceed if we have data
25
- if len(data) > 0:
26
- # Handle missing values
27
- for feature in ['genres', 'categories', 'tags', 'platforms', 'positive_ratings', 'negative_ratings', 'price']:
28
- if feature not in data.columns:
29
- data[feature] = ''
30
- elif data[feature].dtype == object: # String columns
31
- data[feature] = data[feature].fillna('')
32
- else:
33
- data[feature] = data[feature].fillna(0) # Numeric columns
34
-
35
- # Add derived features for better recommendations
36
- if 'positive_ratings' in data.columns and 'negative_ratings' in data.columns:
37
- data['positive_ratings'] = pd.to_numeric(data['positive_ratings'], errors='coerce')
38
- data['negative_ratings'] = pd.to_numeric(data['negative_ratings'], errors='coerce')
39
- data.dropna(subset=['positive_ratings', 'negative_ratings'], inplace=True)
40
-
41
- data['rating_ratio'] = data['positive_ratings'] / (data['positive_ratings'] + data['negative_ratings'] + 1)
42
 
43
- else:
44
- data['rating_ratio'] = 0.5 # Default neutral rating
45
-
46
- # Add playtime features if available - FIX: Added proper checks for empty data
47
- if 'average_playtime_forever' in data.columns:
48
- # Log transform to handle skewed distribution
49
- data['log_playtime'] = np.log1p(data['average_playtime_forever'])
50
-
51
- # FIX: Check if we have valid data before scaling
52
- if len(data) > 0 and not data['log_playtime'].isna().all():
53
- try:
54
- scaler = MinMaxScaler()
55
- # FIX: Only scale non-NA values
56
- valid_playtime_mask = ~data['log_playtime'].isna()
57
- if valid_playtime_mask.any(): # Only if we have any valid values
58
- data.loc[valid_playtime_mask, 'playtime_scaled'] = scaler.fit_transform(
59
- data.loc[valid_playtime_mask, ['log_playtime']]
60
- )
61
- else:
62
- data['playtime_scaled'] = 0.5
63
- except Exception as e:
64
- print(f"Error in playtime scaling: {e}")
65
- data['playtime_scaled'] = 0.5
66
- else:
67
- data['playtime_scaled'] = 0.5
68
- else:
69
- data['playtime_scaled'] = 0.5
70
-
71
- # Add user score features if available - FIX: Added proper checks
72
- if 'user_score' in data.columns:
73
- # FIX: Handle potential non-numeric values
74
- data['user_score'] = pd.to_numeric(data['user_score'], errors='coerce')
75
- # FIX: Check for NaN values before scaling
76
- data['user_score_scaled'] = data['user_score'].fillna(50) / 100.0 # Assuming user_score is out of 100
77
- else:
78
- data['user_score_scaled'] = 0.5
79
-
80
- # Create a more comprehensive combined feature set with weighted components
81
- data['combined_features'] = ''
82
-
83
- # Add name with higher weight for better keyword matching
84
- if 'name' in data.columns:
85
- data['combined_features'] += data['name'].astype(str) + ' ' + data['name'].astype(str) + ' '
86
 
87
- # Add genres with higher weight (repeat to increase importance)
88
- if 'genres' in data.columns:
89
- data['combined_features'] += data['genres'].astype(str) + ' ' + data['genres'].astype(str) + ' '
90
 
91
- # Add other features
92
- for feature in ['categories', 'tags', 'platforms']:
93
- if feature in data.columns:
94
- data['combined_features'] += data[feature].astype(str) + ' '
 
 
95
 
96
- # Add developers and publishers if available
97
- for feature in ['developers', 'publishers']:
98
- if feature in data.columns:
99
- data['combined_features'] += data[feature].astype(str) + ' '
100
 
101
- # Clean the combined features
102
- data['combined_features'] = data['combined_features'].str.replace(';', ' ').str.replace("'", '').str.replace('[', '').str.replace(']', '').str.replace('{', '').str.replace('}', '').str.lower()
103
 
104
- # Vectorize with improved parameters
105
- try:
106
- # FIX: Check if we have enough data for vectorization
107
- if len(data) > 1:
108
- # Use more n-grams and increased max_features for better semantic understanding
109
- vectorizer = TfidfVectorizer(
110
- stop_words='english',
111
- ngram_range=(1, 3), # Capture phrases up to 3 words
112
- max_features=10000, # Increase features for more nuanced relationships
113
- min_df=2, # Ignore very rare terms
114
- max_df=0.9 # Ignore very common terms
115
- )
116
- feature_vectors = vectorizer.fit_transform(data['combined_features'])
117
- print(f"Vectorization complete. Shape: {feature_vectors.shape}")
118
- else:
119
- print("Not enough data for vectorization")
120
- feature_vectors = np.zeros((len(data), 1))
121
- except Exception as e:
122
- print(f"Vectorization error: {e}")
123
- feature_vectors = np.zeros((len(data), 1))
124
 
125
- # Normalize ratings with sigmoid-like scaling for better differentiation
126
- if 'positive_ratings' in data.columns and len(data) > 0:
127
- # Log transform to handle skewed distribution of ratings
128
- data['log_ratings'] = np.log1p(data['positive_ratings'])
129
 
130
- # FIX: Check for valid data before scaling
131
- if not data['log_ratings'].isna().all():
132
- try:
133
- scaler = MinMaxScaler()
134
- valid_ratings_mask = ~data['log_ratings'].isna()
135
- if valid_ratings_mask.any():
136
- data.loc[valid_ratings_mask, 'positive_ratings_scaled'] = scaler.fit_transform(
137
- data.loc[valid_ratings_mask, ['log_ratings']]
138
- )
139
- else:
140
- data['positive_ratings_scaled'] = 0
141
- except Exception as e:
142
- print(f"Error in ratings scaling: {e}")
143
- data['positive_ratings_scaled'] = 0
144
- else:
145
- data['positive_ratings_scaled'] = 0
146
- else:
147
- data['positive_ratings_scaled'] = 0
148
-
149
- # Compute similarity matrix with optimizations
150
- if feature_vectors.shape[0] > 1:
151
- try:
152
- if len(data) > 5000:
153
- print("Large dataset detected. Using batched similarity calculation.")
154
- batch_size = 1000
155
- similarity_matrix = np.zeros((len(data), len(data)))
156
-
157
- for i in range(0, len(data), batch_size):
158
- end = min(i + batch_size, len(data))
159
- batch = feature_vectors[i:end]
160
- similarity_matrix[i:end] = cosine_similarity(batch, feature_vectors)
161
-
162
- game_similarity = similarity_matrix
163
- else:
164
- game_similarity = cosine_similarity(feature_vectors)
165
-
166
- print(f"Similarity matrix created. Shape: {game_similarity.shape}")
167
- except Exception as e:
168
- print(f"Similarity calculation error: {e}")
169
- game_similarity = np.eye(len(data))
170
- else:
171
- game_similarity = np.eye(len(data))
172
-
173
- list_of_all_titles = data['name'].tolist()
174
- else:
175
- feature_vectors = np.zeros((0, 0))
176
- game_similarity = np.zeros((0, 0))
177
- list_of_all_titles = []
178
-
179
- # Improved platform detection function
180
- def detect_platforms(platforms_str):
181
- platforms = []
182
-
183
- if isinstance(platforms_str, str):
184
- platforms_str = platforms_str.lower()
185
 
186
- if 'windows' in platforms_str or 'true' in platforms_str:
 
 
187
  platforms.append("Windows")
188
- if any(mac_term in platforms_str for mac_term in ['mac', 'macos', 'osx']):
189
- platforms.append("macOS")
190
- if 'linux' in platforms_str:
191
  platforms.append("Linux")
192
- if any(mobile_term in platforms_str for mobile_term in ['android', 'ios', 'mobile']):
193
- platforms.append("Mobile")
194
- elif isinstance(platforms_str, bool) and platforms_str:
195
- # Handle boolean True values
196
- platforms.append("Windows") # Assuming Windows by default if boolean True
197
-
198
- return platforms if platforms else ["Unknown"]
199
-
200
- # Extract platform information from dataset columns
201
- def get_platforms(row):
202
- platforms = []
203
-
204
- # Check for platform columns from the screenshots (windows, mac, linux)
205
- if 'windows' in row and row['windows']:
206
- platforms.append("Windows")
207
- if 'mac' in row and row['mac']:
208
- platforms.append("macOS")
209
- if 'linux' in row and row['linux']:
210
- platforms.append("Linux")
211
-
212
- # If no platforms detected but there's a platforms field, try that
213
- if not platforms and 'platforms' in row:
214
- platforms = detect_platforms(row['platforms'])
215
-
216
- return platforms if platforms else ["Unknown"]
217
-
218
- # Extract genre information
219
- def extract_genres(genres_str):
220
- if not genres_str or pd.isna(genres_str):
221
- return []
222
-
223
- # Handle different formats that might be in the data
224
- if isinstance(genres_str, str):
225
- # Remove common formatting characters
226
- clean_str = genres_str.replace("'", "").replace("[", "").replace("]", "").replace("{", "").replace("}", "")
227
 
228
- # Try different delimiters
229
- if ',' in clean_str:
230
- return [g.strip() for g in clean_str.split(',') if g.strip()]
231
- elif ';' in clean_str:
232
- return [g.strip() for g in clean_str.split(';') if g.strip()]
 
 
 
233
  else:
234
- return [clean_str]
235
-
236
- return []
237
-
238
- # Create price gauge visualization
239
- def create_price_gauge(game_price, similar_games_prices):
240
- # Add the main game price to the list
241
- all_prices = [game_price] + similar_games_prices
242
-
243
- # Filter out None values and convert to float
244
- all_prices = [float(p) if p is not None else 0 for p in all_prices]
245
-
246
- # Calculate stats
247
- max_price = max(all_prices) if all_prices else 60 # Default max if no prices
248
- avg_price = sum(all_prices) / len(all_prices) if all_prices else 0
249
-
250
- # Create gauge for the main game price
251
- fig = go.Figure(go.Indicator(
252
- mode="gauge+number",
253
- value=game_price if game_price is not None else 0,
254
- title={'text': "Game Price ($)"},
255
- gauge={
256
- 'axis': {'range': [0, max(max_price, 60)]}, # Ensure reasonable scale
257
- 'bar': {'color': "#1DB954"}, # Steam-like green
258
- 'steps': [
259
- {'range': [0, avg_price], 'color': "lightgray"},
260
- {'range': [avg_price, max_price], 'color': "gray"}
261
- ],
262
- 'threshold': {
263
- 'line': {'color': "red", 'width': 4},
264
- 'thickness': 0.75,
265
- 'value': avg_price
266
- }
267
- }
268
- ))
269
-
270
- fig.update_layout(
271
- height=300,
272
- margin=dict(l=20, r=20, t=50, b=20),
273
- )
274
-
275
- return fig
276
-
277
- # Create user ratings visualization
278
- def create_ratings_chart(game_data):
279
- if not isinstance(game_data, dict):
280
- return None
281
-
282
- # Extract ratings data
283
- game_name = game_data.get('name', 'Unknown')
284
- positive = game_data.get('positive', 0)
285
- negative = game_data.get('negative', 0)
286
-
287
- # Calculate percentages
288
- total = positive + negative
289
- if total == 0:
290
- positive_pct = 0
291
- negative_pct = 0
292
- else:
293
- positive_pct = (positive / total) * 100
294
- negative_pct = (negative / total) * 100
295
-
296
- # Create bar chart
297
- fig = go.Figure()
298
-
299
- fig.add_trace(go.Bar(
300
- x=['Positive', 'Negative'],
301
- y=[positive, negative],
302
- text=[f"{positive:,} ({positive_pct:.1f}%)", f"{negative:,} ({negative_pct:.1f}%)"],
303
- textposition='auto',
304
- marker_color=['#66c0f4', '#ff7b7b'] # Steam-like colors
305
- ))
306
-
307
- fig.update_layout(
308
- title=f"User Ratings for {game_name}",
309
- xaxis_title="Rating Type",
310
- yaxis_title="Number of Ratings",
311
- height=300,
312
- margin=dict(l=20, r=20, t=50, b=20),
313
- )
314
 
315
- return fig
316
 
317
- # Enhanced game recommendation function
318
- def recommend_games(user_game_name_input):
319
- if not user_game_name_input or not list_of_all_titles:
320
- return "Please enter a game name and ensure the dataset is loaded.", [], None, None
321
-
322
- # Normalize input for better matching
323
- user_input_cleaned = user_game_name_input.strip().lower()
324
-
325
- # First try exact match (case insensitive)
326
- exact_matches = [title for title in list_of_all_titles if title.lower() == user_input_cleaned]
327
-
328
- if exact_matches:
329
- closest_match = exact_matches[0]
330
- else:
331
- # Try partial match before fuzzy matching
332
- partial_matches = [title for title in list_of_all_titles if user_input_cleaned in title.lower()]
333
-
334
- if partial_matches:
335
- # Sort by length to prefer shorter (more exact) matches
336
- closest_match = sorted(partial_matches, key=len)[0]
337
  else:
338
- # Try fuzzy matching with improved parameters
339
- find_close_match = difflib.get_close_matches(
340
- user_game_name_input,
341
- list_of_all_titles,
342
- n=5, # Get more candidates
343
- cutoff=0.5 # Lower threshold for more possibilities
344
- )
345
-
346
- if not find_close_match:
347
- return f"No match found for '{user_game_name_input}'. Please try another game name.", [], None, None
348
-
349
- # Take the closest match
350
- closest_match = find_close_match[0]
351
 
352
- try:
353
- index_of_the_game = data.loc[data['name'] == closest_match].index[0]
354
-
355
- # Check for valid index
356
- if index_of_the_game >= len(game_similarity):
357
- return f"Found match '{closest_match}' but encountered an indexing error.", [], None, None
358
-
359
- similarity_scores = list(enumerate(game_similarity[index_of_the_game]))
360
-
361
- # Enhanced ranking with hybrid scoring
362
- game_rankings = []
363
- for idx, sim_score in similarity_scores:
364
- if idx == index_of_the_game: # Skip the game itself
365
- continue
366
-
367
- # Get additional factors for hybrid scoring
368
- rating_factor = data.iloc[idx]['positive_ratings_scaled']
369
-
370
- # Calculate genre similarity separately
371
- searched_game_genres = extract_genres(data.iloc[index_of_the_game].get('genres', ''))
372
- current_game_genres = extract_genres(data.iloc[idx].get('genres', ''))
373
-
374
- # Count matching genres
375
- matching_genres = len(set(searched_game_genres) & set(current_game_genres))
376
- genre_factor = matching_genres / max(len(searched_game_genres), 1) if searched_game_genres else 0
377
-
378
- # Add playtime score if available
379
- playtime_factor = data.iloc[idx].get('playtime_scaled', 0)
380
-
381
- # Create hybrid score with weights
382
- hybrid_score = (
383
- 0.60 * sim_score + # Base similarity from TF-IDF vectors
384
- 0.15 * rating_factor + # Rating popularity
385
- 0.15 * genre_factor + # Genre match
386
- 0.10 * playtime_factor # Playtime popularity
387
- )
388
-
389
- game_rankings.append((idx, hybrid_score))
390
-
391
- # Sort by the hybrid score
392
- sorted_similar_games = sorted(game_rankings, key=lambda x: x[1], reverse=True)
393
-
394
- recommendations = []
395
- game_list = []
396
-
397
- # Get searched game details
398
- searched_game = data.iloc[index_of_the_game]
399
-
400
- # Extract genres
401
- searched_game_genres = extract_genres(searched_game.get('genres', ''))
402
- searched_game_genres_display = ", ".join([g for g in searched_game_genres if g])
403
-
404
- # Extract platforms
405
- searched_game_platforms = get_platforms(searched_game)
406
- searched_game_platform_display = ", ".join(searched_game_platforms)
407
-
408
- # Get price
409
- searched_game_price = searched_game.get('price', 0)
410
- searched_game_price_display = f"${searched_game_price:.2f}" if isinstance(searched_game_price, (int, float)) else "N/A"
411
-
412
- # Get ratings
413
- searched_game_positive = searched_game.get('positive_ratings', searched_game.get('positive', 0))
414
- searched_game_negative = searched_game.get('negative_ratings', searched_game.get('negative', 0))
415
-
416
- # Get metacritic score if available
417
- metacritic_score = searched_game.get('metacritic_score', 'N/A')
418
- metacritic_display = f"{metacritic_score}/100" if metacritic_score != 'N/A' else "N/A"
419
-
420
- # Get user score if available
421
- user_score = searched_game.get('user_score', 'N/A')
422
- user_score_display = f"{user_score}/100" if user_score != 'N/A' else "N/A"
423
-
424
- # Get playtime if available
425
- avg_playtime = searched_game.get('average_playtime_forever', 0)
426
- playtime_display = f"{avg_playtime} minutes" if avg_playtime > 0 else "N/A"
427
-
428
- # Format the searched game with clean styling
429
- recommendations.append(f"## You searched for: {closest_match}\n" +
430
- f"**Genres:** {searched_game_genres_display}\n" +
431
- f"**Platforms:** {searched_game_platform_display}\n" +
432
- f"**Price:** {searched_game_price_display}\n" +
433
- f"**Metacritic Score:** {metacritic_display}\n" +
434
- f"**User Score:** {user_score_display}\n" +
435
- f"**Average Playtime:** {playtime_display}\n")
436
-
437
- game_list.append(closest_match)
438
-
439
- # Add a divider
440
- recommendations.append("---\n## Top Recommendations\n")
441
-
442
- # Get prices and ratings for similar games (for gauge visualization)
443
- similar_games_prices = []
444
-
445
- # Create ratings data for visualization
446
- ratings_data = {
447
- 'name': closest_match,
448
- 'positive': searched_game_positive,
449
- 'negative': searched_game_negative
450
- }
451
-
452
- # Process recommendations with diversity enforcement
453
- seen_publishers = set()
454
- if 'publishers' in data.columns:
455
- searched_game_publisher = str(searched_game.get('publishers', '')).lower()
456
- seen_publishers.add(searched_game_publisher)
457
-
458
- recommended_count = 0
459
-
460
- # Process recommendations
461
- for i, (index, score) in enumerate(sorted_similar_games):
462
- if score < 0.10: # Minimum threshold for quality
463
- continue
464
-
465
- # Enforce diversity by limiting games from same publisher
466
- if 'publishers' in data.columns:
467
- current_publisher = str(data.iloc[index].get('publishers', '')).lower()
468
- if current_publisher in seen_publishers and len(seen_publishers) > 2:
469
- continue
470
- seen_publishers.add(current_publisher)
471
-
472
- game_name = data.iloc[index]['name']
473
-
474
- # Get platform info
475
- platform_list = get_platforms(data.iloc[index])
476
- platform_display = ", ".join(platform_list)
477
-
478
- # Get price info
479
- price = data.iloc[index].get('price', 0)
480
- similar_games_prices.append(price)
481
- price_display = f"${price:.2f}" if isinstance(price, (int, float)) else "N/A"
482
-
483
- # Get genre info
484
- genres = extract_genres(data.iloc[index].get('genres', ''))
485
- genres_display = ", ".join([g for g in genres if g])
486
-
487
- # Get metacritic score if available
488
- rec_metacritic_score = data.iloc[index].get('metacritic_score', 'N/A')
489
- rec_metacritic_display = f"{rec_metacritic_score}/100" if rec_metacritic_score != 'N/A' else "N/A"
490
-
491
- # Get user score if available
492
- rec_user_score = data.iloc[index].get('user_score', 'N/A')
493
- rec_user_score_display = f"{rec_user_score}/100" if rec_user_score != 'N/A' else "N/A"
494
-
495
- # Calculate match percentage
496
- match_percentage = min(int(score * 100), 100) # Cap at 100%
497
-
498
- # Format recommendation with clean styling
499
- position = recommended_count + 1
500
- recommendation = (
501
- f"### {position}. {game_name}\n" +
502
- f"**Match:** {match_percentage}%\n" +
503
- f"**Genres:** {genres_display}\n" +
504
- f"**Platforms:** {platform_display}\n" +
505
- f"**Price:** {price_display}\n" +
506
- f"**Metacritic Score:** {rec_metacritic_display}\n" +
507
- f"**User Score:** {rec_user_score_display}\n"
508
- )
509
-
510
- recommendations.append(recommendation)
511
- game_list.append(game_name)
512
- recommended_count += 1
513
-
514
- if recommended_count >= 5: # Stop after 5 recommendations
515
- break
516
-
517
- # Create price gauge visualization
518
- price_gauge = create_price_gauge(searched_game_price, similar_games_prices)
519
-
520
- # Create ratings chart
521
- ratings_chart = create_ratings_chart(ratings_data)
522
-
523
- return "\n".join(recommendations), game_list, price_gauge, ratings_chart
524
-
525
- except Exception as e:
526
- return f"Error while finding recommendations: {str(e)}", [], None, None
527
 
528
- # Gradio UI with improved design
529
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
530
  gr.Markdown("# Steam Game Recommender")
531
- gr.Markdown("Enter the name of a game you like and get recommendations based on similarity analysis of our Steam games dataset.")
532
 
533
  with gr.Row():
534
- with gr.Column(scale=4):
535
- input_box = gr.Textbox(
536
- label="Your Favorite Game",
537
- placeholder="e.g., Counter-Strike, PUBG, Dota 2, Grand Theft Auto V",
538
- info="Type a game name that exists in the Steam dataset"
539
- )
540
- with gr.Column(scale=1):
541
- run_button = gr.Button("Find Recommendations", variant="primary")
542
 
543
- with gr.Tabs():
544
- with gr.TabItem("Recommendations"):
545
- with gr.Row():
546
- with gr.Column(scale=3):
547
- # Recommendations output
548
- output_text = gr.Markdown(label="Recommendations")
549
- with gr.Column(scale=2):
550
- with gr.Row():
551
- # Price gauge visualization
552
- price_gauge = gr.Plot(label="Price Comparison")
553
- with gr.Row():
554
- # Ratings chart
555
- ratings_chart = gr.Plot(label="User Ratings")
556
-
557
- with gr.TabItem("About"):
558
- gr.Markdown("""
559
- ## About This Recommender
560
-
561
- This Steam game recommender system uses machine learning to find games similar to your favorites. It analyzes:
562
-
563
- - Game genres and categories
564
- - User ratings and reviews
565
- - Platform availability
566
- - Tags and game descriptions
567
- - Price points
568
- - Player statistics
569
-
570
- The recommendations are based on a hybrid scoring system that combines content similarity, user ratings, and gameplay metrics.
571
-
572
- For best results, enter the exact name of a game that exists in the Steam database.
573
- """)
574
-
575
- # Register event
576
- def on_submit(user_input):
577
- rec_text, game_list, gauge, ratings = recommend_games(user_input)
578
- return rec_text, gauge, ratings
579
-
580
- run_button.click(
581
- fn=on_submit,
582
- inputs=input_box,
583
- outputs=[output_text, price_gauge, ratings_chart],
584
- show_progress=True
585
- )
586
 
587
- # Also trigger on Enter key
588
- input_box.submit(
589
- fn=on_submit,
590
- inputs=input_box,
591
- outputs=[output_text, price_gauge, ratings_chart],
592
- show_progress=True
 
 
 
 
 
 
 
 
593
  )
594
 
595
- # Launch the Gradio app
596
  if __name__ == "__main__":
597
  demo.launch()
 
 
1
  import pandas as pd
 
2
  import difflib
 
3
  from sklearn.feature_extraction.text import TfidfVectorizer
4
  from sklearn.metrics.pairwise import cosine_similarity
5
+ import gradio as gr
6
+ import numpy as np
7
 
8
+ # Load the data - when deploying, adjust the path to where your dataset will be stored
9
+ def load_data():
10
  try:
11
+ # For Hugging Face Spaces deployment, you might need to adjust this path
12
+ data = pd.read_csv('games_march2025_cleaned.csv', nrows=20000, on_bad_lines='skip', engine='python')
13
  return data
14
  except Exception as e:
15
  print(f"Error loading data: {e}")
16
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ # Prepare the feature vectors for similarity calculation
19
+ def prepare_features(data):
20
+ selected_features = ['genres', 'price', 'average_playtime_2weeks', 'tags', 'average_playtime_forever']
21
+
22
+ for feature in selected_features:
23
+ data[feature] = data[feature].fillna('')
24
+
25
+ combined_features = (
26
+ data['genres'] + ' ' +
27
+ data['price'].astype(str) + ' ' +
28
+ data['average_playtime_2weeks'].astype(str) + ' ' +
29
+ data['tags'].astype(str)
30
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ vectorizer = TfidfVectorizer()
33
+ feature_vectors = vectorizer.fit_transform(combined_features)
 
34
 
35
+ return feature_vectors
36
+
37
+ # Function to get game recommendations
38
+ def get_recommendations(game_name, data, feature_vectors):
39
+ list_of_all_titles = data['name'].tolist()
40
+ find_close_match = difflib.get_close_matches(game_name, list_of_all_titles)
41
 
42
+ if not find_close_match:
43
+ return "No match found for the game name. Please try another title."
 
 
44
 
45
+ closest_match = find_close_match[0]
46
+ index_of_the_game = data.loc[data['name'] == closest_match].index[0]
47
 
48
+ game_similarity = cosine_similarity(feature_vectors)
49
+ similarity_scores = list(enumerate(game_similarity[index_of_the_game]))
50
+ sorted_similar_games = sorted(similarity_scores, key=lambda x: x[1], reverse=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ results = []
53
+ for i, game in enumerate(sorted_similar_games[1:10], 1): # Skip the first one as it's the game itself
54
+ index = game[0]
55
+ name = data.loc[index, 'name']
56
 
57
+ # Get additional information
58
+ about = data.loc[index, 'about_the_game'] if 'about_the_game' in data.columns else "No description available"
59
+ image_url = data.loc[index, 'header_image'] if 'header_image' in data.columns else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ # Get platform information
62
+ platforms = []
63
+ if 'windows' in data.columns and data.loc[index, 'windows'] == 1:
64
  platforms.append("Windows")
65
+ if 'mac' in data.columns and data.loc[index, 'mac'] == 1:
66
+ platforms.append("Mac")
67
+ if 'linux' in data.columns and data.loc[index, 'linux'] == 1:
68
  platforms.append("Linux")
69
+ platforms_str = ", ".join(platforms) if platforms else "Unknown"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ # Format the result
72
+ result = f"**{i}. {name}**\n\n"
73
+ result += f"**Platforms:** {platforms_str}\n\n"
74
+
75
+ # Truncate the about text to keep output clean
76
+ if about and about != "":
77
+ about_truncated = about[:300] + "..." if len(about) > 300 else about
78
+ result += f"**About the Game:** {about_truncated}\n\n"
79
  else:
80
+ result += "**About the Game:** No description available\n\n"
81
+
82
+ result += "---\n\n"
83
+
84
+ results.append((result, image_url))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
+ return results
87
 
88
+ # Gradio interface function
89
+ def recommend_games(game_name):
90
+ data = load_data()
91
+ if data is None:
92
+ return "Failed to load data. Please check the data file."
93
+
94
+ feature_vectors = prepare_features(data)
95
+ recommendations = get_recommendations(game_name, data, feature_vectors)
96
+
97
+ if isinstance(recommendations, str):
98
+ return recommendations
99
+
100
+ # Format the output for Gradio
101
+ result_texts = []
102
+ result_images = []
103
+
104
+ for result, image_url in recommendations:
105
+ result_texts.append(result)
106
+ if image_url and str(image_url) != 'nan':
107
+ result_images.append(image_url)
108
  else:
109
+ # Use a placeholder image if no image URL is available
110
+ result_images.append(None)
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ # Create a gallery of results
113
+ results_html = ""
114
+ for i, (text, img) in enumerate(zip(result_texts, result_images)):
115
+ results_html += text
116
+
117
+ return results_html, result_images
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ # Create the Gradio interface
120
+ with gr.Blocks(title="Steam Game Recommender") as demo:
121
  gr.Markdown("# Steam Game Recommender")
122
+ gr.Markdown("Enter your favorite game to get recommendations for similar games.")
123
 
124
  with gr.Row():
125
+ input_text = gr.Textbox(label="Enter your favorite game:")
126
+ submit_btn = gr.Button("Get Recommendations")
 
 
 
 
 
 
127
 
128
+ with gr.Row():
129
+ output_text = gr.Markdown(label="Recommendations")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
+ with gr.Row():
132
+ output_gallery = gr.Gallery(
133
+ label="Game Images",
134
+ show_label=True,
135
+ elem_id="gallery",
136
+ columns=[3],
137
+ rows=[3],
138
+ height="auto"
139
+ )
140
+
141
+ submit_btn.click(
142
+ fn=recommend_games,
143
+ inputs=input_text,
144
+ outputs=[output_text, output_gallery]
145
  )
146
 
147
+ # Launch the app
148
  if __name__ == "__main__":
149
  demo.launch()