CoolFace
Apppublic

jthorrigan/letterboxd-recommender

sourceHugging Facemitupdated 8mo agoView on Hugging Face
0likes
visualizations.py264 linesDownload Raw Back to root
1"""2Visualization module for movie watching patterns3Creates interactive charts using Plotly4"""5import pandas as pd6import plotly.graph_objects as go7import plotly.express as px8from typing import Optional9from config import PLOT_HEIGHT, PLOT_TEMPLATE10 11 12class MovieVisualizer:13    """Create visualizations for movie watching patterns"""14    15    def __init__(self, ratings_df: pd.DataFrame):16        self.ratings_df = ratings_df17        18    def plot_rating_distribution(self) -> go.Figure:19        """Create histogram of rating distribution"""20        if 'rating' not in self.ratings_df.columns:21            return self._create_empty_plot("No rating data available")22        23        ratings = self.ratings_df['rating'].dropna()24        25        fig = go.Figure()26        fig.add_trace(go.Histogram(27            x=ratings,28            nbinsx=10,29            name='Ratings',30            marker_color='#00c030'31        ))32        33        fig.update_layout(34            title="Rating Distribution",35            xaxis_title="Rating (0.5 - 5.0)",36            yaxis_title="Number of Movies",37            height=PLOT_HEIGHT,38            template=PLOT_TEMPLATE,39            showlegend=False40        )41        42        return fig43    44    def plot_ratings_over_time(self) -> go.Figure:45        """Plot ratings over time"""46        if 'date' not in self.ratings_df.columns or 'rating' not in self.ratings_df.columns:47            return self._create_empty_plot("No date or rating data available")48        49        df = self.ratings_df[['date', 'rating']].dropna()50        51        if len(df) == 0:52            return self._create_empty_plot("No valid date/rating data")53        54        df = df.sort_values('date')55        56        # Calculate rolling average57        df['rolling_avg'] = df['rating'].rolling(window=10, min_periods=1).mean()58        59        fig = go.Figure()60        61        # Individual ratings62        fig.add_trace(go.Scatter(63            x=df['date'],64            y=df['rating'],65            mode='markers',66            name='Individual Ratings',67            marker=dict(size=6, color='#00c030', opacity=0.5)68        ))69        70        # Rolling average71        fig.add_trace(go.Scatter(72            x=df['date'],73            y=df['rolling_avg'],74            mode='lines',75            name='10-Movie Average',76            line=dict(color='#ff8000', width=3)77        ))78        79        fig.update_layout(80            title="Ratings Over Time",81            xaxis_title="Date",82            yaxis_title="Rating",83            height=PLOT_HEIGHT,84            template=PLOT_TEMPLATE,85            hovermode='closest'86        )87        88        return fig89    90    def plot_watch_frequency(self) -> go.Figure:91        """Plot number of movies watched over time"""92        if 'date' not in self.ratings_df.columns:93            return self._create_empty_plot("No date data available")94        95        df = self.ratings_df[['date']].dropna()96        97        if len(df) == 0:98            return self._create_empty_plot("No valid date data")99        100        # Group by month101        df['year_month'] = df['date'].dt.to_period('M')102        monthly_counts = df.groupby('year_month').size().reset_index(name='count')103        monthly_counts['date'] = monthly_counts['year_month'].dt.to_timestamp()104        105        fig = go.Figure()106        fig.add_trace(go.Bar(107            x=monthly_counts['date'],108            y=monthly_counts['count'],109            name='Movies Watched',110            marker_color='#00c030'111        ))112        113        fig.update_layout(114            title="Movies Watched Per Month",115            xaxis_title="Date",116            yaxis_title="Number of Movies",117            height=PLOT_HEIGHT,118            template=PLOT_TEMPLATE,119            showlegend=False120        )121        122        return fig123    124    def plot_year_distribution(self) -> go.Figure:125        """Plot distribution of movie years watched"""126        if 'year' not in self.ratings_df.columns:127            return self._create_empty_plot("No year data available")128        129        years = self.ratings_df['year'].dropna()130        131        if len(years) == 0:132            return self._create_empty_plot("No valid year data")133        134        # Group into decades135        decade_counts = years.apply(lambda x: (x // 10) * 10).value_counts().sort_index()136        137        fig = go.Figure()138        fig.add_trace(go.Bar(139            x=[f"{int(d)}s" for d in decade_counts.index],140            y=decade_counts.values,141            name='Movies',142            marker_color='#00c030'143        ))144        145        fig.update_layout(146            title="Movies Watched by Decade",147            xaxis_title="Decade",148            yaxis_title="Number of Movies",149            height=PLOT_HEIGHT,150            template=PLOT_TEMPLATE,151            showlegend=False152        )153        154        return fig155    156    def plot_rating_trends(self) -> go.Figure:157        """Analyze if user is getting harsher or more generous over time"""158        if 'date' not in self.ratings_df.columns or 'rating' not in self.ratings_df.columns:159            return self._create_empty_plot("No date or rating data available")160        161        df = self.ratings_df[['date', 'rating']].dropna()162        163        if len(df) < 10:164            return self._create_empty_plot("Need at least 10 ratings for trend analysis")165        166        df = df.sort_values('date')167        168        # Split into periods169        n = len(df)170        period_size = max(n // 5, 10)  # At least 10 movies per period171        172        periods = []173        period_avgs = []174        175        for i in range(0, n, period_size):176            period_df = df.iloc[i:i+period_size]177            if len(period_df) > 0:178                periods.append(f"Period {len(periods)+1}")179                period_avgs.append(period_df['rating'].mean())180        181        fig = go.Figure()182        fig.add_trace(go.Scatter(183            x=periods,184            y=period_avgs,185            mode='lines+markers',186            name='Average Rating',187            line=dict(color='#00c030', width=3),188            marker=dict(size=10)189        ))190        191        # Add trend line192        if len(periods) > 1:193            import numpy as np194            x_numeric = list(range(len(periods)))195            z = np.polyfit(x_numeric, period_avgs, 1)196            p = np.poly1d(z)197            198            fig.add_trace(go.Scatter(199                x=periods,200                y=p(x_numeric),201                mode='lines',202                name='Trend',203                line=dict(color='#ff8000', width=2, dash='dash')204            ))205        206        fig.update_layout(207            title="Rating Trends Over Time",208            xaxis_title="Time Period",209            yaxis_title="Average Rating",210            height=PLOT_HEIGHT,211            template=PLOT_TEMPLATE212        )213        214        return fig215    216    def create_summary_stats(self) -> str:217        """Create summary statistics text"""218        if len(self.ratings_df) == 0:219            return "No data available"220        221        stats = f"## ๐Ÿ“Š Summary Statistics\n\n"222        stats += f"**Total Movies Rated:** {len(self.ratings_df)}\n\n"223        224        if 'rating' in self.ratings_df.columns:225            ratings = self.ratings_df['rating'].dropna()226            if len(ratings) > 0:227                stats += f"**Average Rating:** {ratings.mean():.2f} / 5.0\n\n"228                stats += f"**Most Common Rating:** {ratings.mode().iloc[0]:.1f}\n\n"229                stats += f"**Rating Range:** {ratings.min():.1f} - {ratings.max():.1f}\n\n"230        231        if 'year' in self.ratings_df.columns:232            years = self.ratings_df['year'].dropna()233            if len(years) > 0:234                stats += f"**Year Range:** {int(years.min())} - {int(years.max())}\n\n"235        236        if 'date' in self.ratings_df.columns:237            dates = self.ratings_df['date'].dropna()238            if len(dates) > 0:239                stats += f"**Watching Period:** {dates.min().strftime('%Y-%m-%d')} to {dates.max().strftime('%Y-%m-%d')}\n\n"240                days = (dates.max() - dates.min()).days241                if days > 0:242                    stats += f"**Movies per Day:** {len(self.ratings_df) / days:.2f}\n\n"243        244        return stats245    246    def _create_empty_plot(self, message: str) -> go.Figure:247        """Create empty plot with message"""248        fig = go.Figure()249        fig.add_annotation(250            text=message,251            xref="paper",252            yref="paper",253            x=0.5,254            y=0.5,255            showarrow=False,256            font=dict(size=16)257        )258        fig.update_layout(259            height=PLOT_HEIGHT,260            template=PLOT_TEMPLATE,261            xaxis=dict(showgrid=False, showticklabels=False),262            yaxis=dict(showgrid=False, showticklabels=False)263        )264        return fig