Datenanalyse#

In diesem Notebook wird die vorbereitete Tabelle mit den Spielen der Season 2021/2022 und zugehörigen Sentimentwerten analysiert. Dafür werden zuerst Diagramme zu alle Spiele und anschließend zu einzelnen Teams erstellt. Das Ziel ist so zu überpüfen, ob ein Zusammenhang zwischen Stimmung und Spielausgang besteht.

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import warnings
warnings.filterwarnings('ignore')
import ipywidgets as widgets
from scipy.stats import ttest_ind
df_summary = pd.read_csv('summary_2021.csv')

Auswertung Season#

Die nachfolgende Treemap ist eine erste Übersicht über die reguläre Season. Es werden die fünf Mannschaften dargestellt, welche pro Woche den höchsten durchschnittlichen Sentiment-Score erreicht haben.

df_top = df_summary[['week', 'team_by_entity', 'avg_score']].sort_values(by=['week','avg_score'], ascending=False).groupby('week').head(5)
df_top
week team_by_entity avg_score
517 18 Vikings 0.440000
539 18 Panthers 0.325000
520 18 Buccaneers 0.300000
518 18 49ers 0.268750
528 18 Lions 0.257143
... ... ... ...
20 1 Rams 0.239130
1 1 Broncos 0.232381
11 1 Eagles 0.222254
5 1 Chargers 0.213907
23 1 Seahawks 0.212987

90 rows × 3 columns

df = px.data.tips()
fig = px.treemap(df_top, path=[px.Constant("all"), 'week', 'team_by_entity'], values='avg_score')
fig.update_traces(root_color="lightgrey")
fig.show()

Die Größe der Fläche lässt darauf schließen, dass Woche 8 und Woche 12 die Wochen mit den höchsten durchschnittlichen Sentimenscores waren. Dagegen weisen Woche 10 und Woche 3 deutlich geringer durchschnittliche Sentimentwerte auf. In Woche 8 und 12 sind 49ers die Mannschaft mit den höchsten und Jaguars die Mannschaft mit den zweithöchsten Sentiment-Score Dies wird im nachfolgenden Linienplot überprüft:

fig = px.line(df_summary, x="week", y="avg_score", color = "team_by_entity")
fig.show()

In dem obenstehenden Plot werden die durchschnittlichen Sentiment-Scores aller Teams über die Season 2021/2022 dargestellt. Mit der Legende können einzelnen Mannschaften ein- oder ausgeblendet werden.

In der Regel liegen die Sentiment-Scores aller Mannschaften zwischen -0.2 und 0.3. Auffällig sind lokale Hochpunkte bei den Vikings in Woche 18, bei den Jaguars in Woche 8 und bei den 49ers in Woche 12, wie auch in der Treemap gezeigt. Lokale Tiefpunkte befinden sich bei den Lions in Woche 6, bei den Patriots in Woche 18 und bei den Panthers in Woche 17.

Im vorhergenden Graphen ist erkennbar, dass ab Woche 4 wesentlich mehr Streuung in den Avg-Scores enthalten ist. Dies kann an der Kappungsgrenze auf 80 Kommentare je Video ab Woche 4 liegen. D.h. die Anzahl der Kommentare könnte einen Einfluss auf die Streuung des Durchschnittswerts haben. Daher wird nachfolgend die Anzahl der Kommentare untersucht.

fig = px.bar(df_summary, x='week',  y=["count_neutral", "count_pos", "count_neg"])
fig.show()

Wie vermutet, übersteigt die Anzahl der Kommentare bis Woche 4 deutlich alle anderen Wochen. Das kann zur Verzerrrung des Ergebnisses führen. Aus diesem Grund werden nachfolgend nur die Wochen 5-18 betrachetet.

df_summary = df_summary[df_summary['week'] > 4]

Betrachtet man die restlichen Wochen, verteilen sich die Kommentare der Mannschaften auf ein Sepkturm von 66 (Jaguars) und 239 (Bills) Kommentaren pro Team. Dabei erhielten die Bills die meisten positiven und neutralen Kommentare. Die Texans dagegen führen das Balkendiagram mit den meisten negativen Kommentaren an.

fig = px.bar(df_summary, x='team_by_entity',  y=["count_neutral", "count_pos", "count_neg"])
fig.show()

Die oben erwähnten lokalen Mimimum- und Maximumpunkte werden im folgenden näher untersucht.

  • Maximum

    • Vikings in Woche 18

    • Jaguars in Woche 8

    • 49ers in Woche 12

  • Minimum

    • Lions in Woche 6

    • Patriots in Woche 18

    • Panthers in Woche 17

panthers = df_summary[df_summary["team_by_entity"]=="Panthers"]

# Create figure with secondary y-axis
fig = make_subplots(specs=[[{"secondary_y": True}]])

# Add traces
fig.add_trace(
    go.Bar(name='Postiv', x=panthers.week, y=panthers.count_pos))
    
fig.add_trace(go.Bar(name='Negative', x=panthers.week, y=panthers.count_neg)
)

fig.add_trace(go.Bar(name='Neutral', x=panthers.week, y=panthers.count_neutral)
)

fig.add_trace(
    go.Scatter(x=panthers.week, y=panthers.avg_score, name="Average Score"),
    secondary_y=True,
)

# Add figure title
fig.update_layout(
    title_text="Example Panthers"
)

# Set x-axis title
fig.update_xaxes(title_text="Week")

# Set y-axes titles
fig.update_yaxes(title_text="<b>primary</b> Count comments", secondary_y=False)
fig.update_yaxes(title_text="<b>secondary</b> Average Sentiment Score", secondary_y=True)

fig.show()

Bei dem erwähnten Spiel der Panthers steht der geringe Sentimentscore mit einer sehr geringen Anzahl an Kommentaren im Zusammenhang (ein negativer Kommentar). Bei den lokalen Extrempunkten der Jaguars, 49ers und Patriots ist es ähnlich. Durch die Kommentargrenze konnten hier zuwenig Kommentare mit der jeweiligen Entität ausgewertet werden. Für die Spiele der Lions und Vikings liegen an den Ausreißern fünf oder mehr Kommentare vor. Für die weitere Analyse werden nur Spiele mit mindestens fünf Kommentaren betrachtet, um die Verzerrung zu minimieren. Optimal wäre ein Abruf aller Kommentare und deren Auswertung zu Entitäten ohne Kommentargrenze.

df_summary= df_summary[df_summary['count_neg']+df_summary['count_pos']+df_summary['count_neg']>5]
df_summary
Unnamed: 0 team_by_entity week outcome avg_score count_neg count_pos count_neutral
131 504 Texans 5 defeat -0.087500 4 1 11
134 335 Lions 5 defeat 0.050000 4 3 5
135 64 Bills 5 win 0.113333 2 12 16
136 471 Seahawks 5 defeat 0.025000 4 5 7
137 164 Chiefs 5 defeat 0.160000 1 15 14
... ... ... ... ... ... ... ... ...
536 93 Browns 18 win 0.010000 5 5 10
537 399 Raiders 18 win 0.128000 3 10 11
538 42 Bengals 18 defeat 0.017647 3 4 10
541 467 Seahawks 18 win 0.079167 1 7 15
542 177 Colts 18 defeat -0.042105 7 7 5

227 rows × 8 columns

Um die Leistung eines NFL-Teams zu bewerten, wird in der NFL der PCT verwendet. Der PCT beschreibt den prozentualen Anteil gewonnener Spiele in der regulären Season. Dieser wird im folgenden für alle Mannschaften berechnet mit der oben genannten Einschränkung von min. fünf Kommentaren.

def count_outcome(inp_series, outcome_value): 
    try: 
        count = inp_series.value_counts()[outcome_value]
    except: 
        count = 0
    return count
pct= df_summary.groupby(['team_by_entity']).agg(
                                    win=('outcome', lambda x: count_outcome(x, 'win')),
                                    total=('outcome', 'count'), 
                                    score=('avg_score', 'mean'))
pct['percentage'] = pct['win']/pct['total']
pct.reset_index(inplace=True)
pct
team_by_entity win total score percentage
0 49ers 3 5 0.125774 0.600000
1 Bears 1 4 -0.037500 0.250000
2 Bengals 5 7 0.105042 0.714286
3 Bills 6 9 0.117300 0.666667
4 Broncos 4 7 0.034295 0.571429
5 Browns 3 8 0.064443 0.375000
6 Buccaneers 2 4 0.012188 0.500000
7 Cardinals 5 6 0.113523 0.833333
8 Chargers 4 10 0.081163 0.400000
9 Chiefs 4 6 0.108142 0.666667
10 Colts 6 9 0.077324 0.666667
11 Commanders 2 7 0.028464 0.285714
12 Cowboys 8 10 0.151034 0.800000
13 Dolphins 5 7 0.133151 0.714286
14 Eagles 6 9 0.143781 0.666667
15 Falcons 4 7 0.024146 0.571429
16 Giants 1 7 -0.057548 0.142857
17 Jaguars 0 2 -0.146825 0.000000
18 Jets 2 12 0.009864 0.166667
19 Lions 3 9 0.062045 0.333333
20 Packers 3 4 0.081875 0.750000
21 Panthers 0 4 -0.021550 0.000000
22 Patriots 3 5 0.141779 0.600000
23 Raiders 3 5 0.017519 0.600000
24 Rams 2 5 -0.030654 0.400000
25 Ravens 3 5 0.077857 0.600000
26 Saints 6 11 0.068283 0.545455
27 Seahawks 4 11 0.026876 0.363636
28 Steelers 5 10 0.090859 0.500000
29 Texans 2 9 -0.028649 0.222222
30 Titans 8 9 0.155556 0.888889
31 Vikings 3 4 0.022054 0.750000

Es soll untersucht werden, ob ein Zusammenhang zwischen dem durchschnittlichen Sentiment und dem PCT, bezogen auf die Gesamtleistung, in der Season besteht. Dafür wird im ersten Schritt die Beziehung graphisch in einem Scatterplot untersucht. Die Punkteverteilung und die eingezeichnet Trendlinie lässt einen linearen Zusammenhang vermuten.

fig = px.scatter(pct, x= "percentage", y="score", hover_data=['team_by_entity'], trendline="ols", trendline_color_override="red")
fig.show()

Im zweiten Schritt wird der Zusammenhang mit einem linearen Regression-Modell überprüft. Dazu wird eine in plotly integrierte statsmodel-Funktion genutzt. Der Fokus der Regressionsbewertung liegt auf R-squared und P-Value zu x1.

results = px.get_trendline_results(fig)
print(results)
                                      px_fit_results
0  <statsmodels.regression.linear_model.Regressio...
results.px_fit_results.iloc[0].summary()
OLS Regression Results
Dep. Variable: y R-squared: 0.648
Model: OLS Adj. R-squared: 0.636
Method: Least Squares F-statistic: 55.18
Date: Mon, 09 Jan 2023 Prob (F-statistic): 2.81e-08
Time: 18:40:11 Log-Likelihood: 56.685
No. Observations: 32 AIC: -109.4
Df Residuals: 30 BIC: -106.4
Df Model: 1
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
const -0.0661 0.018 -3.689 0.001 -0.103 -0.030
x1 0.2395 0.032 7.428 0.000 0.174 0.305
Omnibus: 2.232 Durbin-Watson: 1.879
Prob(Omnibus): 0.328 Jarque-Bera (JB): 1.798
Skew: -0.432 Prob(JB): 0.407
Kurtosis: 2.224 Cond. No. 5.43


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

In der Bewertung des Modells bestätigt der P-Wert einen linearen Zusammenhang. Der R-squared ist jedoch sehr niedrig. Aus diesem Grund lässt sich ein Zusammenhang zwischen dem durchschnittlichen Sentimentscore und Leistung der Mannschaft nicht eindeutig nachweisen. Möglicherweise wird die Güte des Modells (R-squared) deutlich besser, wenn wie oben beschrieben alle Kommentare zur Analyse genutzt werden.

Auswertung Mannschaft#

In den bisherigen Abschnitten wird die gesamte Season betrachtet. Im Kapitel “Auswertung Mannschaft” liegt der Fokus auf einer Mannschaft und deren Leistung im Verlauf der Season. Über das Dropdown-Menü-Widget kann eine Mannschaft ausgewählt werden. Standardmäßig sind Los Angeles Chargers ausgewählt.

liste_teams = df_summary['team_by_entity'].unique()
widget = widgets.Dropdown(
    options= liste_teams,
    value='Chargers',
    description='Number:',
    disabled=False,
)
display(widget)
widget.value
'Chargers'
team_df = df_summary[df_summary['team_by_entity'] == widget.value]
team_df
Unnamed: 0 team_by_entity week outcome avg_score count_neg count_pos count_neutral
173 150 Chargers 6 defeat 0.075000 5 6 5
243 151 Chargers 8 defeat 0.016667 3 1 2
308 138 Chargers 11 win 0.094737 4 6 9
343 139 Chargers 12 defeat -0.116667 4 0 8
370 140 Chargers 13 win 0.145455 2 11 9
411 141 Chargers 14 win 0.223077 1 8 4
441 142 Chargers 15 defeat 0.138095 3 7 10
478 143 Chargers 16 defeat 0.005263 3 3 13
500 144 Chargers 17 win 0.230000 1 4 5
532 145 Chargers 18 defeat 0.000000 4 3 6

Ím ersten Plot wird der Anteil an positiven, negativen und neutralen Kommentaren pro Spiel dargestellt.

fig = go.Figure()

fig.add_trace(go.Scatter(
    x=team_df['week'], y=team_df['count_pos'],
    mode='lines',
    line=dict(width=0.5, color='rgb(184, 247, 212)'),
    stackgroup='one',
    groupnorm='percent', # sets the normalization for the sum of the stackgroup
    name = "positive"
))
fig.add_trace(go.Scatter(
    x=team_df['week'], y=team_df['count_neutral'],
    mode='lines',
    line=dict(width=0.5, color='rgb(111, 231, 219)'),
    stackgroup='one', 
    name = "neutral"
))
fig.add_trace(go.Scatter(
    x=team_df['week'], y=team_df['count_neg'],
    mode='lines',
    line=dict(width=0.5, color='rgb(127, 166, 238)'),
    stackgroup='one', 
    name = "negative"
))

fig.update_layout(
    showlegend=True,
    xaxis_type='category',
    xaxis_title= "week",
    yaxis_title="percentage comments",
    yaxis=dict(
        type='linear',
        range=[1, 100],
        ticksuffix='%'))

fig.show()

In Woche 8 erhielten die Chargers zum Beispiel prozentual die meisten negativen Kommentare und in Woche 14 die meisten positven Kommentare. Die Flächenverhältnisse vermitteln eine schwankende Stimmung im Zeitverlauf. Im Folgenden soll untersucht werden, ob es ein Zusammenhang mit den Spielergebnissen in dieser Season gibt.

team_df['total_number'] = team_df['count_pos']+team_df['count_neg']+team_df['count_neutral']
fig = px.scatter(team_df, x="week", y="count_pos", color="outcome", size = 'total_number')
fig.show()

Der Scatterplot stellt die Woche, die Anzahl der positiven Kommentare, den Ausgang und die gesamte Anzahl an Kommentaren (Bubble-Größe) dar. Die meisten positiven Kommentare verbunden mit einem Sieg gab es in Woche 13. In der Woche 12 ohne postive Kommentare haben die Chargers verloren. Ein Zusammenhang lässt sich trotzdem nicht klar sehen, da zum Beispiel Woche 17 siegreich, aber mit wenigen positiven Kommentaren war. Die Bubble-Größe lässt, aber auch auf wenige Kommentare in dieser Woche schließen.
Hinweis: Woche 7, Woche 9 und Woche 10 fehlen, da weniger als 5 Kommentare mit Entität Chargers ausgelesen wurden.

fig = px.box(team_df, x="outcome", y="avg_score")
fig.show()

In den Bloxplot-Diagrammen wird die Verteilung des Average-Sentimentscores pro Spiel nach Sieg und Niederlage gruppiert. Der Median der siegreichen Spiele ist deutlich höher als der der Niederlagen-Box. Der obere Whisker der Niederlagen liegt unter diesem. Die Verteilungen von Win und Defeat lassen einen Unterschied vermuten, welcher nachfolgend noch durch einen t-Test untersucht wird.

Der t-Test wird wie folgt interpretiert:

Nullhypothese: Es gibt keinen signifikanten Sentiment-Unterschied bei Sieg oder Niederlage.
AltHypothese: Es gibt einen signifikanten Sentiment-Unterschied bei Sieg oder Niederlage.

group1 = team_df[team_df['outcome']== 'win']
group2 = team_df[team_df['outcome']== 'defeat']
pvalue= ttest_ind(group1['avg_score'], group2['avg_score']).pvalue
if (pvalue < 0.05):
    print (pvalue)
    print ("Es gibt einen signifikaten Unterschied zwischen Sieg und Niederlage in der Stimmung. Die Nullhypothese wird verworfen.")
else:
    print (pvalue)
    print ("Es gibt keinen signifikaten Unterschied zwischen Sieg und Niederlage in der Stimmung.Die Nullhypothese wird nicht verworfen.")
0.01599320301638785
Es gibt einen signifikaten Unterschied zwischen Sieg und Niederlage in der Stimmung. Die Nullhypothese wird verworfen

Wie bei den bisherigen Betrachtungen ist zu bedenken, dass die Kommentaranzahl gering ist. Ebenfalls wurde eine Normalverteilung nur angenommen.