Spaces:
Runtime error
Runtime error
| import pandas as pd | |
| import difflib | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| import gradio as gr | |
| import numpy as np | |
| import re | |
| from PIL import Image | |
| from io import BytesIO | |
| import requests | |
| # Load the data - when deploying, adjust the path to where your dataset will be stored | |
| def load_data(): | |
| try: | |
| # For Hugging Face Spaces deployment, you might need to adjust this path | |
| data = pd.read_csv('games_march2025_cleaned.csv', nrows=20000, on_bad_lines='skip', engine='python') | |
| return data | |
| except Exception as e: | |
| print(f"Error loading data: {e}") | |
| return None | |
| # Prepare the feature vectors for similarity calculation | |
| def prepare_features(data): | |
| selected_features = ['genres', 'price', 'average_playtime_2weeks', 'tags', 'average_playtime_forever'] | |
| for feature in selected_features: | |
| data[feature] = data[feature].fillna('') | |
| combined_features = ( | |
| data['genres'] + ' ' + | |
| data['price'].astype(str) + ' ' + | |
| data['average_playtime_2weeks'].astype(str) + ' ' + | |
| data['tags'].astype(str) | |
| ) | |
| vectorizer = TfidfVectorizer() | |
| feature_vectors = vectorizer.fit_transform(combined_features) | |
| return feature_vectors | |
| # Function to get game recommendations | |
| def get_recommendations(game_name, data, feature_vectors): | |
| list_of_all_titles = data['name'].tolist() | |
| find_close_match = difflib.get_close_matches(game_name, list_of_all_titles) | |
| if not find_close_match: | |
| return "No match found for the game name. Please try another title." | |
| closest_match = find_close_match[0] | |
| index_of_the_game = data.loc[data['name'] == closest_match].index[0] | |
| game_similarity = cosine_similarity(feature_vectors) | |
| similarity_scores = list(enumerate(game_similarity[index_of_the_game])) | |
| sorted_similar_games = sorted(similarity_scores, key=lambda x: x[1], reverse=True) | |
| results = [] | |
| for i, game in enumerate(sorted_similar_games[1:10], 1): # Skip the first one as it's the game itself | |
| index = game[0] | |
| name = data.loc[index, 'name'] | |
| # Get additional information | |
| about = data.loc[index, 'about_the_game'] if 'about_the_game' in data.columns else "No description available" | |
| # Try to get an image - first check screenshots, then header_image | |
| image_url = None | |
| if 'screenshots' in data.columns and pd.notna(data.loc[index, 'screenshots']): | |
| # Try to extract the first screenshot URL | |
| screenshots = data.loc[index, 'screenshots'] | |
| if isinstance(screenshots, str): | |
| # Handle potential JSON format | |
| if screenshots.startswith('[') and ']' in screenshots: | |
| try: | |
| import json | |
| screenshot_list = json.loads(screenshots) | |
| if screenshot_list and isinstance(screenshot_list, list) and len(screenshot_list) > 0: | |
| if isinstance(screenshot_list[0], dict) and 'path_full' in screenshot_list[0]: | |
| image_url = screenshot_list[0]['path_full'] | |
| elif isinstance(screenshot_list[0], str): | |
| image_url = screenshot_list[0] | |
| except: | |
| # If JSON parsing fails, try regex | |
| url_match = re.search(r'https?://[^\s,\'"]+\.(jpg|jpeg|png|gif)', screenshots) | |
| if url_match: | |
| image_url = url_match.group(0) | |
| # If no screenshot, try header image | |
| if (image_url is None or image_url == '') and 'header_image' in data.columns: | |
| image_url = data.loc[index, 'header_image'] if pd.notna(data.loc[index, 'header_image']) else None | |
| # Get platform information | |
| platforms = [] | |
| if 'windows' in data.columns and data.loc[index, 'windows'] == 1: | |
| platforms.append("Windows") | |
| if 'mac' in data.columns and data.loc[index, 'mac'] == 1: | |
| platforms.append("Mac") | |
| if 'linux' in data.columns and data.loc[index, 'linux'] == 1: | |
| platforms.append("Linux") | |
| platforms_str = ", ".join(platforms) if platforms else "Unknown" | |
| # Get price information | |
| price = data.loc[index, 'price'] if 'price' in data.columns else None | |
| price_str = f"${price}" if pd.notna(price) and price != '' else "Price not available" | |
| # Format the result | |
| result = f"**{name}**\n\n" | |
| result += f"**Price:** {price_str}\n" | |
| result += f"**Platforms:** {platforms_str}\n\n" | |
| # Add genres if available | |
| if 'genres' in data.columns and pd.notna(data.loc[index, 'genres']): | |
| genres = data.loc[index, 'genres'] | |
| if genres and genres != '': | |
| # Clean up genres format | |
| if isinstance(genres, str): | |
| # Handle potential JSON format | |
| if genres.startswith('[') and ']' in genres: | |
| try: | |
| import json | |
| genres_list = json.loads(genres) | |
| if isinstance(genres_list, list): | |
| genres = ", ".join(genres_list) | |
| except: | |
| pass | |
| result += f"**Genres:** {genres}\n\n" | |
| # Truncate and clean the about text | |
| if about and about != "": | |
| # Remove HTML tags | |
| about_clean = re.sub(r'<.*?>', '', about) | |
| about_truncated = about_clean[:300] + "..." if len(about_clean) > 300 else about_clean | |
| result += f"**About the Game:** {about_truncated}\n" | |
| else: | |
| result += "**About the Game:** No description available\n" | |
| results.append((result, image_url)) | |
| return results | |
| # Function to safely load image from URL | |
| def load_image_safely(url): | |
| if not url or str(url).lower() == 'nan': | |
| return None | |
| try: | |
| response = requests.get(url, timeout=5) | |
| if response.status_code == 200: | |
| return Image.open(BytesIO(response.content)) | |
| else: | |
| return None | |
| except: | |
| return None | |
| # Gradio interface function | |
| def recommend_games(game_name): | |
| data = load_data() | |
| if data is None: | |
| return "Failed to load data. Please check the data file." | |
| feature_vectors = prepare_features(data) | |
| recommendations = get_recommendations(game_name, data, feature_vectors) | |
| if isinstance(recommendations, str): | |
| return recommendations | |
| # Format the output for Gradio | |
| result_texts = [] | |
| result_images = [] | |
| for result, image_url in recommendations: | |
| result_texts.append(result) | |
| # Add similarity score if available | |
| if image_url and str(image_url) != 'nan': | |
| # For Hugging Face Spaces, use the URL directly | |
| # The image loading will happen through the browser | |
| result_images.append(image_url) | |
| else: | |
| # Use a placeholder image if no image URL is available | |
| result_images.append(None) | |
| # Return list of recommendations with their info | |
| return result_texts, result_images | |
| # Create the Gradio interface with individual game cards | |
| def create_recommendation_ui(game_name): | |
| data = load_data() | |
| if data is None: | |
| return [gr.Markdown("Failed to load data. Please check the data file.")] | |
| feature_vectors = prepare_features(data) | |
| recommendations = get_recommendations(game_name, data, feature_vectors) | |
| if isinstance(recommendations, str): | |
| return [gr.Markdown(recommendations)] | |
| result_texts, result_images = recommendations | |
| # Create output components dynamically | |
| output_components = [] | |
| for i, (text, img_url) in enumerate(zip(result_texts, result_images)): | |
| with gr.Group(): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| if img_url and str(img_url) != 'nan': | |
| output_components.append(gr.Image(value=img_url, label=f"Game {i+1}")) | |
| else: | |
| output_components.append(gr.Markdown("*No image available*")) | |
| with gr.Column(scale=2): | |
| output_components.append(gr.Markdown(text)) | |
| output_components.append(gr.Markdown("---")) | |
| return output_components | |
| with gr.Blocks(title="Steam Game Recommender") as demo: | |
| gr.Markdown("# Steam Game Recommender") | |
| gr.Markdown("Enter your favorite game to get recommendations for similar games.") | |
| with gr.Row(): | |
| input_text = gr.Textbox(label="Enter your favorite game:", placeholder="e.g., Half-Life 2") | |
| submit_btn = gr.Button("Get Recommendations", variant="primary") | |
| output_container = gr.Group(visible=False) | |
| with output_container: | |
| gr.Markdown("## Your Recommendations") | |
| recommendation_outputs = [] | |
| for i in range(9): # For 9 recommendations | |
| with gr.Group(): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| recommendation_outputs.append(gr.Image(label=f"Game {i+1}")) | |
| with gr.Column(scale=2): | |
| recommendation_outputs.append(gr.Markdown()) | |
| recommendation_outputs.append(gr.Markdown("---")) | |
| def process_recommendations(game_name): | |
| data = load_data() | |
| if data is None: | |
| return [gr.update(visible=True), gr.update(value="Failed to load data. Please check the data file.")] | |
| feature_vectors = prepare_features(data) | |
| recommendations = get_recommendations(game_name, data, feature_vectors) | |
| if isinstance(recommendations, str): | |
| return [gr.update(visible=True), gr.update(value=recommendations)] | |
| result_texts, result_images = recommendations | |
| updates = [gr.update(visible=True)] | |
| for i, (text, img_url) in enumerate(zip(result_texts, result_images)): | |
| updates.append(gr.update(value=img_url if img_url and str(img_url) != 'nan' else None)) | |
| updates.append(gr.update(value=text)) | |
| updates.append(gr.update()) | |
| # Fill any remaining slots with empty updates | |
| while len(updates) < len(recommendation_outputs) + 1: | |
| updates.append(gr.update(visible=False)) | |
| return updates | |
| submit_btn.click( | |
| fn=process_recommendations, | |
| inputs=input_text, | |
| outputs=[output_container] + recommendation_outputs | |
| ) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo.launch() |