Datenanalyse
Contents
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()
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.