Bnava13 commited on
Commit
aa642f2
·
verified ·
1 Parent(s): a1eeb05

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +377 -119
app.py CHANGED
@@ -1,14 +1,17 @@
1
  import pandas as pd
2
  import gradio as gr
3
  import plotly.express as px
 
4
  from sklearn.feature_extraction.text import TfidfVectorizer
5
  from sklearn.metrics.pairwise import cosine_similarity
6
  from sklearn.preprocessing import MinMaxScaler
7
  import difflib
8
  import numpy as np
 
 
9
 
10
  # Load dataset with proper error handling
11
- def load_data(file_path='steam.csv', max_rows=20000):
12
  try:
13
  data = pd.read_csv(file_path, quotechar='"', on_bad_lines='skip', nrows=max_rows)
14
  print(f"Successfully loaded {len(data)} games from {file_path}")
@@ -16,7 +19,7 @@ def load_data(file_path='steam.csv', max_rows=20000):
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', 'steamspy_tags', 'platforms', 'positive_ratings'])
20
 
21
  # Load and preprocess data
22
  data = load_data()
@@ -24,7 +27,7 @@ data = load_data()
24
  # Only proceed if we have data
25
  if len(data) > 0:
26
  # Handle missing values more carefully
27
- for feature in ['genres', 'categories', 'steamspy_tags', 'platforms', 'positive_ratings']:
28
  if feature not in data.columns:
29
  data[feature] = ''
30
  elif data[feature].dtype == object: # Only fill string columns with empty strings
@@ -32,12 +35,13 @@ if len(data) > 0:
32
  else:
33
  data[feature] = data[feature].fillna(0) # Fill numeric columns with 0
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 with error handling
@@ -92,6 +96,9 @@ else:
92
  game_similarity = np.zeros((0, 0))
93
  list_of_all_titles = []
94
 
 
 
 
95
  # Platform detection function with improved logic
96
  def detect_platforms(platforms_str):
97
  platforms_str = str(platforms_str).lower()
@@ -107,10 +114,137 @@ def detect_platforms(platforms_str):
107
 
108
  return os_icons if os_icons else ["❓ Unknown"]
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  # Recommend function with improved error handling and consistent return structure
111
  def recommend_games(user_game_name_input):
 
 
 
 
112
  if not user_game_name_input or not list_of_all_titles:
113
- return "Please enter a game name and ensure the dataset is loaded.", None, [], {}
114
 
115
  # Normalize input for better matching
116
  user_input_cleaned = user_game_name_input.strip().lower()
@@ -124,7 +258,7 @@ def recommend_games(user_game_name_input):
124
  # Try fuzzy matching if no exact match
125
  find_close_match = difflib.get_close_matches(user_game_name_input, list_of_all_titles, n=1, cutoff=0.6)
126
  if not find_close_match:
127
- return f"No match found for '{user_game_name_input}'. Please try another game name.", None, [], {}
128
  closest_match = find_close_match[0]
129
 
130
  try:
@@ -132,7 +266,7 @@ def recommend_games(user_game_name_input):
132
 
133
  # Check for valid index
134
  if index_of_the_game >= len(game_similarity):
135
- return f"Found match '{closest_match}' but encountered an indexing error.", None, [], {}
136
 
137
  similarity_scores = list(enumerate(game_similarity[index_of_the_game]))
138
 
@@ -144,11 +278,11 @@ def recommend_games(user_game_name_input):
144
  )
145
 
146
  recommendations = []
147
- chart_data = []
148
- radar_data = {}
149
 
150
  # Add the searched game as the first entry
151
  recommendations.append(f"✓ You searched for: {closest_match}")
 
152
 
153
  # Process recommendations
154
  for i, (index, score) in enumerate(sorted_similar_games[1:21]):
@@ -162,28 +296,34 @@ def recommend_games(user_game_name_input):
162
  os_list = detect_platforms(platforms)
163
  os_display = " | ".join(os_list)
164
 
165
- # Format recommendation with emoji
166
- recommendations.append(f"{i+1}. {game_name} ({os_display}) - {score*100:.1f}% similar")
 
167
 
168
- # Store data for charts
169
- chart_data.append({'Game': game_name, 'Similarity': score * 100})
 
170
 
171
- # Store data for radar chart
172
- genres = data.iloc[index].get('genres', '')
173
- categories = data.iloc[index].get('categories', '')
174
- radar_data[game_name] = {
175
- "genres": genres,
176
- "categories": categories
177
- }
 
 
 
178
 
179
  if len(recommendations) >= 11: # 10 recommendations + original search
180
  break
181
 
182
- chart_df = pd.DataFrame(chart_data) if chart_data else None
183
- return "\n".join(recommendations), chart_df, list(radar_data.keys()), radar_data
 
184
 
185
  except Exception as e:
186
- return f"Error while finding recommendations: {str(e)}", None, [], {}
187
 
188
  # Improved precision calculation
189
  def evaluate_precision(user_game_name_input):
@@ -225,65 +365,51 @@ def evaluate_precision(user_game_name_input):
225
  print(f"Precision calculation error: {str(e)}")
226
  return 0.0
227
 
228
- # Improved radar plot function
229
- def plot_game_features(game_name, radar_data):
230
- if not game_name or game_name not in radar_data:
231
- return None
232
-
233
  try:
234
- # Extract features
235
- genres = str(radar_data[game_name]["genres"]).split(';') if radar_data[game_name]["genres"] else []
236
- categories = str(radar_data[game_name]["categories"]).split(';') if radar_data[game_name]["categories"] else []
237
 
238
- # Clean and deduplicate features
239
- features = list(set([g.strip() for g in genres + categories if g.strip()]))
 
240
 
241
- # Limit to top features for readability
242
- if len(features) > 10:
243
- features = features[:10]
244
-
245
- if not features:
246
- return None
247
-
248
- values = [1] * len(features)
249
- radar_df = pd.DataFrame({
250
- 'Feature': features,
251
- 'Presence': values
252
- })
253
-
254
- fig = px.line_polar(radar_df, r='Presence', theta='Feature', line_close=True,
255
- title=f"Feature Radar: {game_name}", range_r=[0, 1])
256
- fig.update_traces(fill='toself')
257
- return fig
258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  except Exception as e:
260
- print(f"Radar chart error: {str(e)}")
261
  return None
262
 
263
  # Combined function with progress updates
264
  def recommend_and_visualize(user_input):
265
  if not user_input or user_input.strip() == "":
266
- return "Please enter a game name", None, [], {}
267
 
268
  # Get recommendations
269
- recommendations, chart_df, game_names, radar_data = recommend_games(user_input)
270
 
271
  # Calculate precision
272
  precision = evaluate_precision(user_input)
273
 
274
- # Create chart if data is available
275
- chart = None
276
- if chart_df is not None and not chart_df.empty:
277
- try:
278
- chart = px.bar(chart_df, x="Game", y="Similarity",
279
- title="Top Game Recommendations",
280
- labels={"Similarity": "Similarity (%)"},
281
- height=400)
282
- # Improve readability of labels
283
- chart.update_layout(xaxis_tickangle=-45)
284
- except Exception as e:
285
- print(f"Chart creation error: {str(e)}")
286
-
287
  # Add platform legend and precision info
288
  footer = "\n\n📊 **Recommendation Quality**: "
289
  footer += f"Precision@5: {precision*100:.0f}%" if precision > 0 else "Unable to calculate precision"
@@ -291,81 +417,213 @@ def recommend_and_visualize(user_input):
291
  footer += "\n\n**Platform Legend**:\n"
292
  footer += "🖥️ Windows | 🍎 macOS | 🐧 Linux | ❓ Unknown"
293
 
294
- return recommendations + footer, chart, game_names, radar_data
295
 
296
- # Trigger radar chart display
297
- def show_selected_game_radar(game_name, radar_data):
298
  if not game_name:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  return None
300
- return plot_game_features(game_name, radar_data)
301
 
302
- # Improved Gradio UI
303
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
304
  gr.Markdown("# 🎮 Steam Game Recommender")
305
  gr.Markdown("Enter the name of a game you like and get recommendations based on similarity!")
306
 
307
- with gr.Row():
308
- with gr.Column(scale=4):
309
- input_box = gr.Textbox(
310
- label="Your Favorite Game",
311
- placeholder="e.g., Portal 2, Half-Life 2, Skyrim",
312
- info="Type a game name that exists in the Steam dataset"
313
- )
314
- with gr.Column(scale=1):
315
- run_button = gr.Button("Find Recommendations", variant="primary")
316
-
317
- with gr.Accordion("Status", open=False):
318
- status_text = gr.Markdown(f"Dataset loaded: {len(data)} games")
319
-
320
- with gr.Tabs():
321
- with gr.TabItem("Recommendations"):
322
- with gr.Row():
323
- with gr.Column(scale=2):
324
- output_text = gr.Textbox(
325
- label="Recommendations",
326
- lines=15,
327
- interactive=False
328
- )
329
- with gr.Column(scale=3):
330
- output_chart = gr.Plot(label="Similarity Chart")
331
-
332
- with gr.TabItem("Game Features"):
333
- with gr.Row():
334
- with gr.Column(scale=1):
335
- dropdown = gr.Dropdown(
336
- label="Select a Recommended Game to Analyze",
337
- choices=[],
338
- interactive=True,
339
- info="Choose a game to see its features"
340
- )
341
- with gr.Column(scale=3):
342
- radar_chart = gr.Plot(label="Genre & Category Radar")
343
-
344
- # Store radar data between function calls
345
- radar_data_state = gr.State()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
  # Register events
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  run_button.click(
349
- fn=recommend_and_visualize,
350
  inputs=input_box,
351
- outputs=[output_text, output_chart, dropdown, radar_data_state],
352
  show_progress=True
353
  )
354
 
355
  # Also trigger on Enter key
356
  input_box.submit(
357
- fn=recommend_and_visualize,
358
  inputs=input_box,
359
- outputs=[output_text, output_chart, dropdown, radar_data_state],
360
  show_progress=True
361
  )
362
 
 
363
  dropdown.change(
364
- fn=show_selected_game_radar,
365
- inputs=[dropdown, radar_data_state],
366
- outputs=radar_chart
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  )
368
 
369
- # Launch the app
370
  if __name__ == "__main__":
371
  demo.launch()
 
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=10000):
15
  try:
16
  data = pd.read_csv(file_path, quotechar='"', on_bad_lines='skip', nrows=max_rows)
17
  print(f"Successfully loaded {len(data)} games from {file_path}")
 
19
  except Exception as e:
20
  print(f"Error loading data: {e}")
21
  # Return empty DataFrame with expected columns to avoid crashing
22
+ return pd.DataFrame(columns=['name', 'genres', 'categories', 'steamspy_tags', 'platforms', 'positive_ratings', 'price'])
23
 
24
  # Load and preprocess data
25
  data = load_data()
 
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
 
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
 
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()
 
114
 
115
  return os_icons if os_icons else ["❓ Unknown"]
116
 
117
+ # Get game details for display
118
+ def get_game_details(game_name):
119
+ if not game_name or game_name not in list_of_all_titles:
120
+ return "Game not found in database."
121
+
122
+ try:
123
+ game_data = data.loc[data['name'] == game_name].iloc[0]
124
+
125
+ # Get genres and format them
126
+ genres = str(game_data.get('genres', 'Unknown'))
127
+ genres_list = [g.strip() for g in genres.split(';') if g.strip()]
128
+ genres_display = ", ".join(genres_list) if genres_list else "Unknown"
129
+
130
+ # Get price and format it
131
+ price = game_data.get('price', 0)
132
+ if isinstance(price, (int, float)):
133
+ price_display = f"${price:.2f}" if price > 0 else "Free to Play"
134
+ else:
135
+ price_display = "Price unknown"
136
+
137
+ # Get platforms
138
+ platforms = game_data.get('platforms', '')
139
+ os_list = detect_platforms(platforms)
140
+ platforms_display = " | ".join(os_list)
141
+
142
+ # Format the details
143
+ details = f"## {game_name}\n\n"
144
+ details += f"**Price:** {price_display}\n\n"
145
+ details += f"**Genres:** {genres_display}\n\n"
146
+ details += f"**Platforms:** {platforms_display}\n\n"
147
+
148
+ # Add rating information if available
149
+ if 'positive_ratings' in game_data:
150
+ pos_ratings = int(game_data.get('positive_ratings', 0))
151
+ details += f"**Positive Ratings:** {pos_ratings:,}\n\n"
152
+
153
+ if 'negative_ratings' in game_data:
154
+ neg_ratings = int(game_data.get('negative_ratings', 0))
155
+ details += f"**Negative Ratings:** {neg_ratings:,}\n\n"
156
+
157
+ # Calculate approval percentage if both values exist
158
+ if pos_ratings + neg_ratings > 0:
159
+ approval_percent = (pos_ratings / (pos_ratings + neg_ratings)) * 100
160
+ details += f"**Approval Rate:** {approval_percent:.1f}%\n\n"
161
+
162
+ # Add release date if available
163
+ if 'release_date' in game_data:
164
+ release_date = game_data.get('release_date', 'Unknown')
165
+ details += f"**Release Date:** {release_date}\n\n"
166
+
167
+ # Add developer/publisher if available
168
+ if 'developer' in game_data:
169
+ developer = game_data.get('developer', 'Unknown')
170
+ details += f"**Developer:** {developer}\n\n"
171
+
172
+ if 'publisher' in game_data:
173
+ publisher = game_data.get('publisher', 'Unknown')
174
+ details += f"**Publisher:** {publisher}\n\n"
175
+
176
+ return details
177
+
178
+ except Exception as e:
179
+ return f"Error retrieving game details: {str(e)}"
180
+
181
+ # Generate a radar chart for game comparison
182
+ def generate_game_comparison_chart(game_name):
183
+ if not game_name or game_name not in list_of_all_titles:
184
+ return None
185
+
186
+ try:
187
+ # Get the game index
188
+ game_idx = data.loc[data['name'] == game_name].index[0]
189
+
190
+ # Get top 3 similar games
191
+ similarity_scores = list(enumerate(game_similarity[game_idx]))
192
+ sorted_similar = sorted(similarity_scores, key=lambda x: x[1], reverse=True)[1:4] # Skip the first one (the game itself)
193
+
194
+ similar_games = [data.iloc[idx]['name'] for idx, _ in sorted_similar]
195
+
196
+ # Create feature vectors for radar chart (using genres as features)
197
+ features = ['Action', 'Adventure', 'RPG', 'Strategy', 'Simulation', 'Sports', 'Racing']
198
+ chart_data = []
199
+
200
+ # Add main game
201
+ main_game_data = data.iloc[game_idx]
202
+ main_genres = str(main_game_data.get('genres', '')).split(';')
203
+ main_values = [1 if genre in main_genres else 0.2 for genre in features]
204
+ chart_data.append(go.Scatterpolar(
205
+ r=main_values,
206
+ theta=features,
207
+ fill='toself',
208
+ name=game_name
209
+ ))
210
+
211
+ # Add similar games
212
+ for idx, score in sorted_similar:
213
+ sim_game = data.iloc[idx]
214
+ sim_genres = str(sim_game.get('genres', '')).split(';')
215
+ sim_values = [1 if genre in sim_genres else 0.2 for genre in features]
216
+ chart_data.append(go.Scatterpolar(
217
+ r=sim_values,
218
+ theta=features,
219
+ fill='toself',
220
+ name=sim_game['name']
221
+ ))
222
+
223
+ fig = go.Figure(data=chart_data)
224
+ fig.update_layout(
225
+ polar=dict(
226
+ radialaxis=dict(
227
+ visible=True,
228
+ range=[0, 1]
229
+ )
230
+ ),
231
+ showlegend=True,
232
+ title=f"Genre Comparison: {game_name} vs Similar Games"
233
+ )
234
+
235
+ return fig
236
+ except Exception as e:
237
+ print(f"Error generating comparison chart: {e}")
238
+ return None
239
+
240
  # Recommend function with improved error handling and consistent return structure
241
  def recommend_games(user_game_name_input):
242
+ # Check cache first
243
+ if user_game_name_input in recommendation_cache:
244
+ return recommendation_cache[user_game_name_input]
245
+
246
  if not user_game_name_input or not list_of_all_titles:
247
+ return "Please enter a game name and ensure the dataset is loaded.", []
248
 
249
  # Normalize input for better matching
250
  user_input_cleaned = user_game_name_input.strip().lower()
 
258
  # Try fuzzy matching if no exact match
259
  find_close_match = difflib.get_close_matches(user_game_name_input, list_of_all_titles, n=1, cutoff=0.6)
260
  if not find_close_match:
261
+ return f"No match found for '{user_game_name_input}'. Please try another game name.", []
262
  closest_match = find_close_match[0]
263
 
264
  try:
 
266
 
267
  # Check for valid index
268
  if index_of_the_game >= len(game_similarity):
269
+ return f"Found match '{closest_match}' but encountered an indexing error.", []
270
 
271
  similarity_scores = list(enumerate(game_similarity[index_of_the_game]))
272
 
 
278
  )
279
 
280
  recommendations = []
281
+ game_list = []
 
282
 
283
  # Add the searched game as the first entry
284
  recommendations.append(f"✓ You searched for: {closest_match}")
285
+ game_list.append(closest_match) # Include the searched game in the list
286
 
287
  # Process recommendations
288
  for i, (index, score) in enumerate(sorted_similar_games[1:21]):
 
296
  os_list = detect_platforms(platforms)
297
  os_display = " | ".join(os_list)
298
 
299
+ # Get price info
300
+ price = data.iloc[index].get('price', 0)
301
+ price_display = f"${price:.2f}" if isinstance(price, (int, float)) and price > 0 else "Free" if price == 0 else "N/A"
302
 
303
+ # Get genre info for additional context
304
+ genres = str(data.iloc[index].get('genres', '')).split(';')
305
+ genres_display = ", ".join(genres[:2]) if len(genres) > 0 and genres[0] else ""
306
 
307
+ # Format recommendation with emoji and more details
308
+ recommendation = f"{i+1}. {game_name} ({price_display}) - {score*100:.1f}% similar"
309
+ if genres_display:
310
+ recommendation += f" [{genres_display}]"
311
+ recommendation += f" {os_display}"
312
+
313
+ recommendations.append(recommendation)
314
+
315
+ # Add to game list
316
+ game_list.append(game_name)
317
 
318
  if len(recommendations) >= 11: # 10 recommendations + original search
319
  break
320
 
321
+ result = ("\n".join(recommendations), game_list)
322
+ recommendation_cache[user_game_name_input] = result # Cache the result
323
+ return result
324
 
325
  except Exception as e:
326
+ return f"Error while finding recommendations: {str(e)}", []
327
 
328
  # Improved precision calculation
329
  def evaluate_precision(user_game_name_input):
 
365
  print(f"Precision calculation error: {str(e)}")
366
  return 0.0
367
 
368
+ # Function to generate price distribution chart
369
+ def generate_price_chart():
 
 
 
370
  try:
371
+ # Filter for reasonable prices (exclude outliers)
372
+ price_data = data[data['price'] < 100].copy()
 
373
 
374
+ # Create price bins
375
+ price_bins = [0, 5, 10, 15, 20, 30, 50, 100]
376
+ price_data['price_category'] = pd.cut(price_data['price'], bins=price_bins, right=False)
377
 
378
+ # Count games in each price bin
379
+ price_counts = price_data['price_category'].value_counts().sort_index()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
 
381
+ # Create bar chart
382
+ fig = px.bar(
383
+ x=[str(cat) for cat in price_counts.index],
384
+ y=price_counts.values,
385
+ labels={'x': 'Price Range ($)', 'y': 'Number of Games'},
386
+ title='Price Distribution of Steam Games',
387
+ color_discrete_sequence=['#1DB954'] # Steam-like green
388
+ )
389
+
390
+ # Update layout
391
+ fig.update_layout(
392
+ xaxis_title='Price Range ($)',
393
+ yaxis_title='Number of Games',
394
+ template='plotly_white'
395
+ )
396
+
397
+ return fig
398
  except Exception as e:
399
+ print(f"Error generating price chart: {e}")
400
  return None
401
 
402
  # Combined function with progress updates
403
  def recommend_and_visualize(user_input):
404
  if not user_input or user_input.strip() == "":
405
+ return "Please enter a game name", []
406
 
407
  # Get recommendations
408
+ recommendations, game_list = recommend_games(user_input)
409
 
410
  # Calculate precision
411
  precision = evaluate_precision(user_input)
412
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  # Add platform legend and precision info
414
  footer = "\n\n📊 **Recommendation Quality**: "
415
  footer += f"Precision@5: {precision*100:.0f}%" if precision > 0 else "Unable to calculate precision"
 
417
  footer += "\n\n**Platform Legend**:\n"
418
  footer += "🖥️ Windows | 🍎 macOS | 🐧 Linux | ❓ Unknown"
419
 
420
+ return recommendations + footer, game_list
421
 
422
+ # Get details for selected game
423
+ def display_game_details(game_name):
424
  if not game_name:
425
+ return "Please select a game to view details."
426
+
427
+ return get_game_details(game_name)
428
+
429
+ # Function to create genre distribution chart
430
+ def create_genre_chart():
431
+ try:
432
+ # Extract all genres
433
+ all_genres = []
434
+ for genres in data['genres'].dropna():
435
+ all_genres.extend([g.strip() for g in str(genres).split(';') if g.strip()])
436
+
437
+ # Get counts
438
+ genre_counts = pd.Series(all_genres).value_counts().nlargest(10)
439
+
440
+ # Create bar chart
441
+ fig = px.bar(
442
+ x=genre_counts.index,
443
+ y=genre_counts.values,
444
+ labels={'x': 'Genre', 'y': 'Number of Games'},
445
+ title='Top 10 Game Genres on Steam',
446
+ color_discrete_sequence=['#66c0f4'] # Steam blue
447
+ )
448
+
449
+ fig.update_layout(
450
+ xaxis_title='Genre',
451
+ yaxis_title='Number of Games',
452
+ template='plotly_white'
453
+ )
454
+
455
+ return fig
456
+ except Exception as e:
457
+ print(f"Error creating genre chart: {e}")
458
  return None
 
459
 
460
+ # Improved Gradio UI with added features
461
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
462
  gr.Markdown("# 🎮 Steam Game Recommender")
463
  gr.Markdown("Enter the name of a game you like and get recommendations based on similarity!")
464
 
465
+ with gr.Tab("Find Recommendations"):
466
+ with gr.Row():
467
+ with gr.Column(scale=4):
468
+ input_box = gr.Textbox(
469
+ label="Your Favorite Game",
470
+ placeholder="e.g., Portal 2, Half-Life 2, Skyrim",
471
+ info="Type a game name that exists in the Steam dataset"
472
+ )
473
+ with gr.Column(scale=1):
474
+ run_button = gr.Button("Find Recommendations", variant="primary")
475
+
476
+ with gr.Row():
477
+ with gr.Column(scale=1):
478
+ output_text = gr.Textbox(
479
+ label="Recommendations",
480
+ lines=15,
481
+ interactive=False
482
+ )
483
+ dropdown = gr.Dropdown(
484
+ label="Select a Game to View Details",
485
+ choices=[],
486
+ interactive=True,
487
+ info="Choose a game to see its details"
488
+ )
489
+ with gr.Column(scale=1):
490
+ game_details = gr.Markdown(
491
+ label="Game Details",
492
+ value="Select a game from the dropdown to view details."
493
+ )
494
+
495
+ with gr.Tab("Statistics"):
496
+ with gr.Row():
497
+ with gr.Column():
498
+ gr.Markdown("## Game Price Distribution")
499
+ price_chart = gr.Plot(value=generate_price_chart())
500
+
501
+ with gr.Column():
502
+ gr.Markdown("## Top Game Genres")
503
+ genre_chart = gr.Plot(value=create_genre_chart())
504
+
505
+ with gr.Row():
506
+ refresh_stats_button = gr.Button("Refresh Statistics")
507
+
508
+ # Add a tab for help/about
509
+ with gr.Tab("About"):
510
+ gr.Markdown("""
511
+ ## About This Recommender
512
+
513
+ This Steam game recommender uses **TF-IDF vectorization** and **cosine similarity** to find games similar to your favorites. The recommendation engine analyzes:
514
+
515
+ - Game genres
516
+ - Categories
517
+ - User-defined tags
518
+ - Platforms
519
+ - Price points
520
+
521
+ The system then ranks games by similarity score and refines results using positive user ratings.
522
+
523
+ ### How to Use
524
+
525
+ 1. Enter the name of a game you enjoy in the search box
526
+ 2. Click "Find Recommendations" to see similar games
527
+ 3. Select any game from the dropdown to view detailed information
528
+ 4. Explore the Statistics tab to see distributions of game prices and genres
529
+
530
+ ### Dataset
531
+
532
+ This system uses a dataset of Steam games with features like:
533
+ - Game title
534
+ - Genres
535
+ - Categories
536
+ - User tags
537
+ - Price
538
+ - Platform compatibility
539
+ - User ratings
540
+
541
+ ### Limitations
542
+
543
+ - Recommendations depend on data quality and completeness
544
+ - The system works best with popular titles that have detailed metadata
545
+ - Very niche or new games may have fewer accurate recommendations
546
+ """)
547
+
548
+ # Add a search history tab
549
+ with gr.Tab("Search History"):
550
+ search_history = gr.Dataframe(
551
+ headers=["Time", "Search Query", "Top Recommendation"],
552
+ datatype=["str", "str", "str"],
553
+ row_count=10,
554
+ col_count=(3, "fixed"),
555
+ value=[]
556
+ )
557
+
558
+ clear_history_button = gr.Button("Clear History")
559
 
560
  # Register events
561
+ search_history_data = []
562
+
563
+ def update_search_history(user_input):
564
+ if not user_input or user_input.strip() == "":
565
+ return search_history_data
566
+
567
+ recommendations, game_list = recommend_games(user_input)
568
+
569
+ # Format timestamp
570
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
571
+
572
+ # Get top recommendation (if any)
573
+ top_rec = game_list[1] if len(game_list) > 1 else "No recommendation found"
574
+
575
+ # Add to history
576
+ search_history_data.append([timestamp, user_input, top_rec])
577
+
578
+ # Keep only the most recent 10 entries
579
+ return search_history_data[-10:]
580
+
581
+ def clear_history():
582
+ search_history_data.clear()
583
+ return []
584
+
585
+ # Combined function to update recommendations and history
586
+ def recommend_and_update_history(user_input):
587
+ rec_text, game_list = recommend_and_visualize(user_input)
588
+ history = update_search_history(user_input)
589
+ return rec_text, game_list, history
590
+
591
  run_button.click(
592
+ fn=recommend_and_update_history,
593
  inputs=input_box,
594
+ outputs=[output_text, dropdown, search_history],
595
  show_progress=True
596
  )
597
 
598
  # Also trigger on Enter key
599
  input_box.submit(
600
+ fn=recommend_and_update_history,
601
  inputs=input_box,
602
+ outputs=[output_text, dropdown, search_history],
603
  show_progress=True
604
  )
605
 
606
+ # Display game details when a game is selected
607
  dropdown.change(
608
+ fn=display_game_details,
609
+ inputs=dropdown,
610
+ outputs=game_details
611
+ )
612
+
613
+ # Clear history button
614
+ clear_history_button.click(
615
+ fn=clear_history,
616
+ inputs=[],
617
+ outputs=[search_history]
618
+ )
619
+
620
+ # Refresh statistics
621
+ refresh_stats_button.click(
622
+ fn=lambda: (generate_price_chart(), create_genre_chart()),
623
+ inputs=[],
624
+ outputs=[price_chart, genre_chart]
625
  )
626
 
627
+ # Launch the Gradio app
628
  if __name__ == "__main__":
629
  demo.launch()