Bnava13 commited on
Commit
111fffd
·
verified ·
1 Parent(s): aeae255

Removed Emojies

Browse files
Files changed (1) hide show
  1. app.py +109 -383
app.py CHANGED
@@ -1,14 +1,11 @@
1
- import pandas as pd
2
  import gradio as gr
3
- import plotly.express as px
 
 
4
  import plotly.graph_objects as go
5
  from sklearn.feature_extraction.text import TfidfVectorizer
6
  from sklearn.metrics.pairwise import cosine_similarity
7
  from sklearn.preprocessing import MinMaxScaler
8
- import difflib
9
- import numpy as np
10
- import os
11
- import time
12
 
13
  # Load dataset with proper error handling
14
  def load_data(file_path='steam.csv', max_rows=27075):
@@ -26,47 +23,44 @@ data = load_data()
26
 
27
  # Only proceed if we have data
28
  if len(data) > 0:
29
- # Handle missing values more carefully
30
  for feature in ['genres', 'categories', 'steamspy_tags', 'platforms', 'positive_ratings', 'price']:
31
  if feature not in data.columns:
32
  data[feature] = ''
33
- elif data[feature].dtype == object: # Only fill string columns with empty strings
34
  data[feature] = data[feature].fillna('')
35
  else:
36
- data[feature] = data[feature].fillna(0) # Fill numeric columns with 0
37
 
38
- # Combine features - now including price
39
  data['combined_features'] = (
40
  data['genres'].astype(str) + ' ' +
41
  data['categories'].astype(str) + ' ' +
42
  data['steamspy_tags'].astype(str) + ' ' +
43
- data['platforms'].astype(str) + ' ' +
44
- data['price'].astype(str) # Add price as a feature
45
  )
46
 
47
- # Vectorize with error handling
48
  try:
49
- vectorizer = TfidfVectorizer(stop_words='english', ngram_range=(1, 2), max_features=8000)
50
  feature_vectors = vectorizer.fit_transform(data['combined_features'])
51
  print(f"Vectorization complete. Shape: {feature_vectors.shape}")
52
  except Exception as e:
53
  print(f"Vectorization error: {e}")
54
- # Create empty feature vectors to avoid crashing
55
  feature_vectors = np.zeros((len(data), 1))
56
 
57
- # Normalize positive ratings safely
58
  if 'positive_ratings' in data.columns and len(data) > 0:
59
  scaler = MinMaxScaler()
60
  data['positive_ratings_scaled'] = scaler.fit_transform(
61
- data[['positive_ratings']].clip(lower=0) # Ensure no negative ratings
62
  )
63
  else:
64
  data['positive_ratings_scaled'] = 0
65
 
66
- # Compute similarity matrix (only if we have enough data)
67
  if feature_vectors.shape[0] > 1:
68
  try:
69
- # Use batched processing for large datasets to reduce memory usage
70
  if len(data) > 5000:
71
  print("Large dataset detected. Using batched similarity calculation.")
72
  batch_size = 1000
@@ -84,103 +78,73 @@ if len(data) > 0:
84
  print(f"Similarity matrix created. Shape: {game_similarity.shape}")
85
  except Exception as e:
86
  print(f"Similarity calculation error: {e}")
87
- # Create identity matrix as fallback
88
  game_similarity = np.eye(len(data))
89
  else:
90
  game_similarity = np.eye(len(data))
91
 
92
  list_of_all_titles = data['name'].tolist()
93
  else:
94
- # Fallbacks for empty data
95
  feature_vectors = np.zeros((0, 0))
96
  game_similarity = np.zeros((0, 0))
97
  list_of_all_titles = []
98
 
99
- # Cache for storing recommendation results to improve performance
100
- recommendation_cache = {}
101
-
102
- # Platform detection function with improved logic
103
  def detect_platforms(platforms_str):
104
  platforms_str = str(platforms_str).lower()
105
- os_icons = []
106
 
107
- # More reliable platform detection
108
  if 'windows' in platforms_str:
109
- os_icons.append("🖥️ Windows")
110
  if any(mac_term in platforms_str for mac_term in ['mac', 'macos', 'osx']):
111
- os_icons.append("🍎 macOS")
112
  if 'linux' in platforms_str:
113
- os_icons.append("🐧 Linux")
114
 
115
- return os_icons if os_icons else ["Unknown"]
116
 
117
- # Generate a radar chart for game comparison
118
- def generate_game_comparison_chart(game_name):
119
- if not game_name or game_name not in list_of_all_titles:
120
- return None
121
 
122
- try:
123
- # Get the game index
124
- game_idx = data.loc[data['name'] == game_name].index[0]
125
-
126
- # Get top 3 similar games
127
- similarity_scores = list(enumerate(game_similarity[game_idx]))
128
- sorted_similar = sorted(similarity_scores, key=lambda x: x[1], reverse=True)[1:4] # Skip the first one (the game itself)
129
-
130
- similar_games = [data.iloc[idx]['name'] for idx, _ in sorted_similar]
131
-
132
- # Create feature vectors for radar chart (using genres as features)
133
- features = ['Action', 'Adventure', 'RPG', 'Strategy', 'Simulation', 'Sports', 'Racing']
134
- chart_data = []
135
-
136
- # Add main game
137
- main_game_data = data.iloc[game_idx]
138
- main_genres = str(main_game_data.get('genres', '')).split(';')
139
- main_values = [1 if genre in main_genres else 0.2 for genre in features]
140
- chart_data.append(go.Scatterpolar(
141
- r=main_values,
142
- theta=features,
143
- fill='toself',
144
- name=game_name
145
- ))
146
-
147
- # Add similar games
148
- for idx, score in sorted_similar:
149
- sim_game = data.iloc[idx]
150
- sim_genres = str(sim_game.get('genres', '')).split(';')
151
- sim_values = [1 if genre in sim_genres else 0.2 for genre in features]
152
- chart_data.append(go.Scatterpolar(
153
- r=sim_values,
154
- theta=features,
155
- fill='toself',
156
- name=sim_game['name']
157
- ))
158
-
159
- fig = go.Figure(data=chart_data)
160
- fig.update_layout(
161
- polar=dict(
162
- radialaxis=dict(
163
- visible=True,
164
- range=[0, 1]
165
- )
166
- ),
167
- showlegend=True,
168
- title=f"Genre Comparison: {game_name} vs Similar Games"
169
- )
170
-
171
- return fig
172
- except Exception as e:
173
- print(f"Error generating comparison chart: {e}")
174
- return None
175
 
176
- # Enhanced recommendation function with better formatting and emojis
177
  def recommend_games(user_game_name_input):
178
- # Check cache first
179
- if user_game_name_input in recommendation_cache:
180
- return recommendation_cache[user_game_name_input]
181
-
182
  if not user_game_name_input or not list_of_all_titles:
183
- return "Please enter a game name and ensure the dataset is loaded.", []
184
 
185
  # Normalize input for better matching
186
  user_input_cleaned = user_game_name_input.strip().lower()
@@ -194,7 +158,7 @@ def recommend_games(user_game_name_input):
194
  # Try fuzzy matching if no exact match
195
  find_close_match = difflib.get_close_matches(user_game_name_input, list_of_all_titles, n=1, cutoff=0.6)
196
  if not find_close_match:
197
- return f"No match found for '{user_game_name_input}'. Please try another game name.", []
198
  closest_match = find_close_match[0]
199
 
200
  try:
@@ -202,7 +166,7 @@ def recommend_games(user_game_name_input):
202
 
203
  # Check for valid index
204
  if index_of_the_game >= len(game_similarity):
205
- return f"Found match '{closest_match}' but encountered an indexing error.", []
206
 
207
  similarity_scores = list(enumerate(game_similarity[index_of_the_game]))
208
 
@@ -221,12 +185,12 @@ def recommend_games(user_game_name_input):
221
  searched_game_genres = str(searched_game.get('genres', '')).split(';')
222
  searched_game_genres_display = ", ".join([g for g in searched_game_genres if g])
223
  searched_game_platforms = detect_platforms(searched_game.get('platforms', ''))
224
- searched_game_platform_display = " ".join(searched_game_platforms)
225
  searched_game_price = searched_game.get('price', 0)
226
- searched_game_price_display = f"${searched_game_price:.2f}" if isinstance(searched_game_price, (int, float)) and searched_game_price > 0 else "Free" if searched_game_price == 0 else "N/A"
227
 
228
- # Format the searched game with nicer styling
229
- recommendations.append(f"## 🎮 You searched for: {closest_match}\n" +
230
  f"**Genres:** {searched_game_genres_display}\n" +
231
  f"**Platforms:** {searched_game_platform_display}\n" +
232
  f"**Price:** {searched_game_price_display}\n")
@@ -234,339 +198,101 @@ def recommend_games(user_game_name_input):
234
  game_list.append(closest_match)
235
 
236
  # Add a divider
237
- recommendations.append("---\n## 🏆 Top Recommendations\n")
238
 
239
- # Generate emoji for each position
240
- position_emojis = ["🥇", "🥈", "🥉", "4️⃣", "5️⃣"]
241
 
242
  # Process recommendations
243
- for i, (index, score) in enumerate(sorted_similar_games[1:11]): # Only process first 10 to find 5 good ones
244
- if score < 0.2: # Higher threshold for better quality
245
  continue
246
 
247
  game_name = data.iloc[index]['name']
248
 
249
  # Get platform info
250
  platforms = data.iloc[index].get('platforms', '')
251
- os_list = detect_platforms(platforms)
252
- os_display = " ".join(os_list)
253
 
254
  # Get price info
255
  price = data.iloc[index].get('price', 0)
256
- price_display = f"${price:.2f}" if isinstance(price, (int, float)) and price > 0 else "Free" if price == 0 else "N/A"
 
257
 
258
- # Get genre info for additional context
259
  genres = str(data.iloc[index].get('genres', '')).split(';')
260
  genres_display = ", ".join([g for g in genres if g])
261
 
262
- # Get positive ratings
263
- positive_ratings = data.iloc[index].get('positive_ratings', 0)
264
-
265
- # Determine emoji based on game type
266
- category_emoji = "🔫" if "Action" in genres_display else "🧙" if "RPG" in genres_display else "🏎️" if "Racing" in genres_display else "🧩" if "Puzzle" in genres_display else "🌍" if "Adventure" in genres_display else "⚔️" if "Strategy" in genres_display else "🏡" if "Simulation" in genres_display else "🎲"
267
-
268
  # Calculate match percentage
269
  match_percentage = int(score * 100)
270
 
271
- # Create match bar
272
- match_bar = "█" * (match_percentage // 10) + "░" * (10 - (match_percentage // 10))
273
-
274
- # Create color indicator based on match percentage
275
- color_indicator = "🟢" if match_percentage >= 80 else "🟡" if match_percentage >= 60 else "🟠" if match_percentage >= 40 else "🔴"
276
-
277
- # Format recommendation with emoji and more details
278
- position_emoji = position_emojis[i] if i < len(position_emojis) else f"{i+1}."
279
-
280
  recommendation = (
281
- f"### {position_emoji} {category_emoji} {game_name}\n" +
282
- f"**Match:** {color_indicator} {match_percentage}% {match_bar}\n" +
283
  f"**Genres:** {genres_display}\n" +
284
- f"**Platforms:** {os_display}\n" +
285
  f"**Price:** {price_display}\n"
286
  )
287
 
288
  recommendations.append(recommendation)
289
-
290
- # Add to game list
291
  game_list.append(game_name)
292
 
293
  if len(recommendations) >= 7: # searched game + divider + 5 recommendations
294
  break
295
 
296
- result = ("\n".join(recommendations), game_list)
297
- recommendation_cache[user_game_name_input] = result # Cache the result
298
- return result
299
-
300
- except Exception as e:
301
- return f"Error while finding recommendations: {str(e)}", []
302
-
303
- # Improved precision calculation
304
- def evaluate_precision(user_game_name_input):
305
- if not user_game_name_input or not list_of_all_titles:
306
- return 0.0
307
-
308
- try:
309
- find_close_match = difflib.get_close_matches(user_game_name_input, list_of_all_titles, n=1, cutoff=0.6)
310
- if not find_close_match:
311
- return 0.0
312
-
313
- closest_match = find_close_match[0]
314
- index_of_the_game = data.loc[data['name'] == closest_match].index[0]
315
-
316
- if index_of_the_game >= len(game_similarity):
317
- return 0.0
318
-
319
- similarity_scores = list(enumerate(game_similarity[index_of_the_game]))
320
- sorted_similar_games = sorted(
321
- similarity_scores,
322
- key=lambda x: (x[1], data.iloc[x[0]]['positive_ratings_scaled']),
323
- reverse=True
324
- )
325
 
326
- # Calculate precision based on genre overlap rather than exact match
327
- top_5_indices = [idx for idx, _ in sorted_similar_games[1:6]]
328
- original_genres = set(data.iloc[index_of_the_game]['genres'].split(';'))
329
-
330
- hits = 0
331
- for idx in top_5_indices:
332
- rec_genres = set(data.iloc[idx]['genres'].split(';'))
333
- # Count as a hit if there's any genre overlap
334
- if original_genres.intersection(rec_genres):
335
- hits += 1
336
-
337
- return round(hits / 5, 2) if top_5_indices else 0.0
338
 
339
  except Exception as e:
340
- print(f"Precision calculation error: {str(e)}")
341
- return 0.0
342
 
343
- # Combined function with progress updates
344
- def recommend_and_visualize(user_input):
345
- if not user_input or user_input.strip() == "":
346
- return "Please enter a game name", []
347
-
348
- # Get recommendations
349
- recommendations, game_list = recommend_games(user_input)
350
-
351
- # Calculate precision
352
- precision = evaluate_precision(user_input)
353
-
354
- # Add platform legend and precision info
355
- footer = "\n\n---\n"
356
- footer += f"📊 **Recommendation Quality:** {precision*100:.0f}% precision" if precision > 0 else "📊 **Recommendation Quality:** Unable to calculate precision"
357
-
358
- return recommendations + footer, game_list
359
-
360
- # Function to generate price distribution chart
361
- def generate_price_chart():
362
- try:
363
- # Filter for reasonable prices (exclude outliers)
364
- price_data = data[data['price'] < 100].copy()
365
-
366
- # Create price bins
367
- price_bins = [0, 5, 10, 15, 20, 30, 50, 100]
368
- price_data['price_category'] = pd.cut(price_data['price'], bins=price_bins, right=False)
369
-
370
- # Count games in each price bin
371
- price_counts = price_data['price_category'].value_counts().sort_index()
372
-
373
- # Create bar chart
374
- fig = px.bar(
375
- x=[str(cat) for cat in price_counts.index],
376
- y=price_counts.values,
377
- labels={'x': 'Price Range ($)', 'y': 'Number of Games'},
378
- title='Price Distribution of Steam Games',
379
- color_discrete_sequence=['#1DB954'] # Steam-like green
380
- )
381
-
382
- # Update layout
383
- fig.update_layout(
384
- xaxis_title='Price Range ($)',
385
- yaxis_title='Number of Games',
386
- template='plotly_white'
387
- )
388
-
389
- return fig
390
- except Exception as e:
391
- print(f"Error generating price chart: {e}")
392
- return None
393
-
394
- # Function to create genre distribution chart
395
- def create_genre_chart():
396
- try:
397
- # Extract all genres
398
- all_genres = []
399
- for genres in data['genres'].dropna():
400
- all_genres.extend([g.strip() for g in str(genres).split(';') if g.strip()])
401
-
402
- # Get counts
403
- genre_counts = pd.Series(all_genres).value_counts().nlargest(10)
404
-
405
- # Create bar chart
406
- fig = px.bar(
407
- x=genre_counts.index,
408
- y=genre_counts.values,
409
- labels={'x': 'Genre', 'y': 'Number of Games'},
410
- title='Top 10 Game Genres on Steam',
411
- color_discrete_sequence=['#66c0f4'] # Steam blue
412
- )
413
-
414
- fig.update_layout(
415
- xaxis_title='Genre',
416
- yaxis_title='Number of Games',
417
- template='plotly_white'
418
- )
419
-
420
- return fig
421
- except Exception as e:
422
- print(f"Error creating genre chart: {e}")
423
- return None
424
-
425
- # Improved Gradio UI with added features
426
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
427
- gr.Markdown("# 🎮 Steam Game Recommender")
428
- gr.Markdown("Enter the name of a game you like and get recommendations based on similarity!")
429
 
430
- with gr.Tab("Find Recommendations"):
431
- with gr.Row():
432
- with gr.Column(scale=4):
433
- input_box = gr.Textbox(
434
- label="Your Favorite Game",
435
- placeholder="e.g., Portal 2, Half-Life 2, Skyrim",
436
- info="Type a game name that exists in the Steam dataset"
437
- )
438
- with gr.Column(scale=1):
439
- run_button = gr.Button("Find Recommendations", variant="primary")
440
-
441
- with gr.Row():
442
- # Changed to markdown for better formatting
443
- output_text = gr.Markdown(
444
- label="Recommendations"
445
  )
446
-
447
- with gr.Tab("Statistics"):
448
- with gr.Row():
449
- with gr.Column():
450
- gr.Markdown("## Game Price Distribution")
451
- price_chart = gr.Plot(value=generate_price_chart())
452
-
453
- with gr.Column():
454
- gr.Markdown("## Top Game Genres")
455
- genre_chart = gr.Plot(value=create_genre_chart())
456
-
457
- with gr.Row():
458
- refresh_stats_button = gr.Button("Refresh Statistics")
459
 
460
- # Add a tab for help/about
461
- with gr.Tab("About"):
462
- gr.Markdown("""
463
- ## About This Recommender
464
-
465
- This Steam game recommender uses **TF-IDF vectorization** and **cosine similarity** to find games similar to your favorites. The recommendation engine analyzes:
466
-
467
- - Game genres
468
- - Categories
469
- - User-defined tags
470
- - Platforms
471
- - Price points
472
-
473
- The system then ranks games by similarity score and refines results using positive user ratings.
474
-
475
- ### How to Use
476
-
477
- 1. Enter the name of a game you enjoy in the search box
478
- 2. Click "Find Recommendations" to see similar games
479
- 3. Explore the Statistics tab to see distributions of game prices and genres
480
-
481
- ### Dataset
482
-
483
- This system uses a dataset of Steam games with features like:
484
- - Game title
485
- - Genres
486
- - Categories
487
- - User tags
488
- - Price
489
- - Platform compatibility
490
- - User ratings
491
-
492
- ### Limitations
493
-
494
- - Recommendations depend on data quality and completeness
495
- - The system works best with popular titles that have detailed metadata
496
- - Very niche or new games may have fewer accurate recommendations
497
- """)
498
-
499
- # Add a search history tab
500
- with gr.Tab("Search History"):
501
- search_history = gr.Dataframe(
502
- headers=["Time", "Search Query", "Top Recommendation"],
503
- datatype=["str", "str", "str"],
504
- row_count=10,
505
- col_count=(3, "fixed"),
506
- value=[]
507
- )
508
-
509
- clear_history_button = gr.Button("Clear History")
510
 
511
- # Register events
512
- search_history_data = []
513
-
514
- def update_search_history(user_input):
515
- if not user_input or user_input.strip() == "":
516
- return search_history_data
517
-
518
- recommendations, game_list = recommend_games(user_input)
519
-
520
- # Format timestamp
521
- timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
522
-
523
- # Get top recommendation (if any)
524
- top_rec = game_list[1] if len(game_list) > 1 else "No recommendation found"
525
-
526
- # Add to history
527
- search_history_data.append([timestamp, user_input, top_rec])
528
-
529
- # Keep only the most recent 10 entries
530
- return search_history_data[-10:]
531
-
532
- def clear_history():
533
- search_history_data.clear()
534
- return []
535
-
536
- # Combined function to update recommendations and history
537
- def recommend_and_update_history(user_input):
538
- rec_text, game_list = recommend_and_visualize(user_input)
539
- history = update_search_history(user_input)
540
- return rec_text, history
541
 
542
  run_button.click(
543
- fn=recommend_and_update_history,
544
  inputs=input_box,
545
- outputs=[output_text, search_history],
546
  show_progress=True
547
  )
548
 
549
  # Also trigger on Enter key
550
  input_box.submit(
551
- fn=recommend_and_update_history,
552
  inputs=input_box,
553
- outputs=[output_text, search_history],
554
  show_progress=True
555
  )
556
-
557
- # Clear history button
558
- clear_history_button.click(
559
- fn=clear_history,
560
- inputs=[],
561
- outputs=[search_history]
562
- )
563
-
564
- # Refresh statistics
565
- refresh_stats_button.click(
566
- fn=lambda: (generate_price_chart(), create_genre_chart()),
567
- inputs=[],
568
- outputs=[price_chart, genre_chart]
569
- )
570
 
571
  # Launch the Gradio app
572
  if __name__ == "__main__":
 
 
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='steam.csv', max_rows=27075):
 
23
 
24
  # Only proceed if we have data
25
  if len(data) > 0:
26
+ # Handle missing values
27
  for feature in ['genres', 'categories', 'steamspy_tags', 'platforms', 'positive_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
+ # Combine features
36
  data['combined_features'] = (
37
  data['genres'].astype(str) + ' ' +
38
  data['categories'].astype(str) + ' ' +
39
  data['steamspy_tags'].astype(str) + ' ' +
40
+ data['platforms'].astype(str)
 
41
  )
42
 
43
+ # Vectorize
44
  try:
45
+ vectorizer = TfidfVectorizer(stop_words='english', ngram_range=(1, 2), max_features=5000)
46
  feature_vectors = vectorizer.fit_transform(data['combined_features'])
47
  print(f"Vectorization complete. Shape: {feature_vectors.shape}")
48
  except Exception as e:
49
  print(f"Vectorization error: {e}")
 
50
  feature_vectors = np.zeros((len(data), 1))
51
 
52
+ # Normalize positive ratings
53
  if 'positive_ratings' in data.columns and len(data) > 0:
54
  scaler = MinMaxScaler()
55
  data['positive_ratings_scaled'] = scaler.fit_transform(
56
+ data[['positive_ratings']].clip(lower=0)
57
  )
58
  else:
59
  data['positive_ratings_scaled'] = 0
60
 
61
+ # Compute similarity matrix
62
  if feature_vectors.shape[0] > 1:
63
  try:
 
64
  if len(data) > 5000:
65
  print("Large dataset detected. Using batched similarity calculation.")
66
  batch_size = 1000
 
78
  print(f"Similarity matrix created. Shape: {game_similarity.shape}")
79
  except Exception as e:
80
  print(f"Similarity calculation error: {e}")
 
81
  game_similarity = np.eye(len(data))
82
  else:
83
  game_similarity = np.eye(len(data))
84
 
85
  list_of_all_titles = data['name'].tolist()
86
  else:
 
87
  feature_vectors = np.zeros((0, 0))
88
  game_similarity = np.zeros((0, 0))
89
  list_of_all_titles = []
90
 
91
+ # Platform detection function without emojis
 
 
 
92
  def detect_platforms(platforms_str):
93
  platforms_str = str(platforms_str).lower()
94
+ platforms = []
95
 
 
96
  if 'windows' in platforms_str:
97
+ platforms.append("Windows")
98
  if any(mac_term in platforms_str for mac_term in ['mac', 'macos', 'osx']):
99
+ platforms.append("macOS")
100
  if 'linux' in platforms_str:
101
+ platforms.append("Linux")
102
 
103
+ return platforms if platforms else ["Unknown"]
104
 
105
+ # Create price gauge visualization
106
+ def create_price_gauge(game_price, similar_games_prices):
107
+ # Add the main game price to the list
108
+ all_prices = [game_price] + similar_games_prices
109
 
110
+ # Filter out None values and convert to float
111
+ all_prices = [float(p) if p is not None else 0 for p in all_prices]
112
+
113
+ # Calculate stats
114
+ max_price = max(all_prices) if all_prices else 60 # Default max if no prices
115
+ avg_price = sum(all_prices) / len(all_prices) if all_prices else 0
116
+
117
+ # Create gauge for the main game price
118
+ fig = go.Figure(go.Indicator(
119
+ mode="gauge+number",
120
+ value=game_price if game_price is not None else 0,
121
+ title={'text': "Game Price ($)"},
122
+ gauge={
123
+ 'axis': {'range': [0, max(max_price, 60)]}, # Ensure reasonable scale
124
+ 'bar': {'color': "#1DB954"}, # Steam-like green
125
+ 'steps': [
126
+ {'range': [0, avg_price], 'color': "lightgray"},
127
+ {'range': [avg_price, max_price], 'color': "gray"}
128
+ ],
129
+ 'threshold': {
130
+ 'line': {'color': "red", 'width': 4},
131
+ 'thickness': 0.75,
132
+ 'value': avg_price
133
+ }
134
+ }
135
+ ))
136
+
137
+ fig.update_layout(
138
+ height=300,
139
+ margin=dict(l=20, r=20, t=50, b=20),
140
+ )
141
+
142
+ return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
+ # Function to get game recommendations
145
  def recommend_games(user_game_name_input):
 
 
 
 
146
  if not user_game_name_input or not list_of_all_titles:
147
+ return "Please enter a game name and ensure the dataset is loaded.", [], None
148
 
149
  # Normalize input for better matching
150
  user_input_cleaned = user_game_name_input.strip().lower()
 
158
  # Try fuzzy matching if no exact match
159
  find_close_match = difflib.get_close_matches(user_game_name_input, list_of_all_titles, n=1, cutoff=0.6)
160
  if not find_close_match:
161
+ return f"No match found for '{user_game_name_input}'. Please try another game name.", [], None
162
  closest_match = find_close_match[0]
163
 
164
  try:
 
166
 
167
  # Check for valid index
168
  if index_of_the_game >= len(game_similarity):
169
+ return f"Found match '{closest_match}' but encountered an indexing error.", [], None
170
 
171
  similarity_scores = list(enumerate(game_similarity[index_of_the_game]))
172
 
 
185
  searched_game_genres = str(searched_game.get('genres', '')).split(';')
186
  searched_game_genres_display = ", ".join([g for g in searched_game_genres if g])
187
  searched_game_platforms = detect_platforms(searched_game.get('platforms', ''))
188
+ searched_game_platform_display = ", ".join(searched_game_platforms)
189
  searched_game_price = searched_game.get('price', 0)
190
+ searched_game_price_display = f"${searched_game_price:.2f}" if isinstance(searched_game_price, (int, float)) else "N/A"
191
 
192
+ # Format the searched game with clean styling
193
+ recommendations.append(f"## You searched for: {closest_match}\n" +
194
  f"**Genres:** {searched_game_genres_display}\n" +
195
  f"**Platforms:** {searched_game_platform_display}\n" +
196
  f"**Price:** {searched_game_price_display}\n")
 
198
  game_list.append(closest_match)
199
 
200
  # Add a divider
201
+ recommendations.append("---\n## Top Recommendations\n")
202
 
203
+ # Get prices for similar games (for gauge visualization)
204
+ similar_games_prices = []
205
 
206
  # Process recommendations
207
+ for i, (index, score) in enumerate(sorted_similar_games[1:6]): # Get top 5 recommendations
208
+ if score < 0.2: # Minimum threshold for quality
209
  continue
210
 
211
  game_name = data.iloc[index]['name']
212
 
213
  # Get platform info
214
  platforms = data.iloc[index].get('platforms', '')
215
+ platform_list = detect_platforms(platforms)
216
+ platform_display = ", ".join(platform_list)
217
 
218
  # Get price info
219
  price = data.iloc[index].get('price', 0)
220
+ similar_games_prices.append(price)
221
+ price_display = f"${price:.2f}" if isinstance(price, (int, float)) else "N/A"
222
 
223
+ # Get genre info
224
  genres = str(data.iloc[index].get('genres', '')).split(';')
225
  genres_display = ", ".join([g for g in genres if g])
226
 
 
 
 
 
 
 
227
  # Calculate match percentage
228
  match_percentage = int(score * 100)
229
 
230
+ # Format recommendation with clean styling
231
+ position = i + 1
 
 
 
 
 
 
 
232
  recommendation = (
233
+ f"### {position}. {game_name}\n" +
234
+ f"**Match:** {match_percentage}%\n" +
235
  f"**Genres:** {genres_display}\n" +
236
+ f"**Platforms:** {platform_display}\n" +
237
  f"**Price:** {price_display}\n"
238
  )
239
 
240
  recommendations.append(recommendation)
 
 
241
  game_list.append(game_name)
242
 
243
  if len(recommendations) >= 7: # searched game + divider + 5 recommendations
244
  break
245
 
246
+ # Create price gauge visualization
247
+ price_gauge = create_price_gauge(searched_game_price, similar_games_prices)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
+ return "\n".join(recommendations), game_list, price_gauge
 
 
 
 
 
 
 
 
 
 
 
250
 
251
  except Exception as e:
252
+ return f"Error while finding recommendations: {str(e)}", [], None
 
253
 
254
+ # Gradio UI with simplified design
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
256
+ gr.Markdown("# Steam Game Recommender")
257
+ gr.Markdown("Enter the name of a game you like and get recommendations based on similarity.")
258
 
259
+ with gr.Row():
260
+ with gr.Column(scale=4):
261
+ input_box = gr.Textbox(
262
+ label="Your Favorite Game",
263
+ placeholder="e.g., Portal 2, Half-Life 2, Skyrim",
264
+ info="Type a game name that exists in the Steam dataset"
 
 
 
 
 
 
 
 
 
265
  )
266
+ with gr.Column(scale=1):
267
+ run_button = gr.Button("Find Recommendations", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
268
 
269
+ with gr.Row():
270
+ with gr.Column(scale=3):
271
+ # Recommendations output
272
+ output_text = gr.Markdown(label="Recommendations")
273
+ with gr.Column(scale=2):
274
+ # Price gauge visualization
275
+ price_gauge = gr.Plot(label="Price Comparison")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
+ # Register event
278
+ def on_submit(user_input):
279
+ rec_text, game_list, gauge = recommend_games(user_input)
280
+ return rec_text, gauge
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
  run_button.click(
283
+ fn=on_submit,
284
  inputs=input_box,
285
+ outputs=[output_text, price_gauge],
286
  show_progress=True
287
  )
288
 
289
  # Also trigger on Enter key
290
  input_box.submit(
291
+ fn=on_submit,
292
  inputs=input_box,
293
+ outputs=[output_text, price_gauge],
294
  show_progress=True
295
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
  # Launch the Gradio app
298
  if __name__ == "__main__":