Kurs
Durch die neue Binge-Watching-Kultur bei Serien und Filmen konsumieren Nutzer Inhalte in rasantem Tempo – dank Diensten wie Netflix, Prime Video, Hulu und Disney+. Einige dieser Plattformen, etwa Hulu und YouTube TV, bieten zudem Livestreams von Sport, Konzerten/Touren und Nachrichtensendern an. Live-Streaming ist bei manchen Plattformen wie Netflix noch nicht etabliert.
Streaming-Plattformen geben Nutzerinnen und Nutzern die Freiheit, ihre Lieblingsserien und -filme jederzeit und auf jedem Gerät anzusehen. Dank der großen Vielfalt an Inhalten ziehen diese Dienste vor allem ein junges, modernes Publikum an. Verpasste Sendungen lassen sich flexibel nachholen. In diesem Tutorial analysierst du Filmdaten der Plattformen Netflix, Prime Video, Hulu und Disney+ und versuchst, deren Zuschauerschaft besser zu verstehen.

Das Dataset verstehen
Die Daten enthalten ausschließlich Filme, die auf Streaming-Plattformen wie Netflix, Prime Video, Hulu und Disney+ verfügbar sind. Du kannst sie bei Kaggle hier herunterladen.
Die Datenattribute im Überblick:
- ID: Eindeutige Kennung für jeden Datensatz.
- Title: Filmtitel
- Year: Erscheinungsjahr
- Age: Zielaltersgruppe
- IMDb: IMDb-Bewertung der Filme.
- Rotten Tomatoes: Rotten-Tomatoes-Prozentwert
- Netflix: Ob der Film auf Netflix verfügbar ist
- Hulu: Ob der Film auf Hulu verfügbar ist
- Prime Video: Ob der Film auf Prime Video verfügbar ist
- Disney+: Ob der Film auf Disney+ verfügbar ist
- Type: Film oder TV-Show
- Directors: Name der Regie
- Genres: Genre-Typ
- Country: Produktionsland
- Language: Originalsprache
- Runtime: Laufzeit des Films
Importieren wir die benötigten Module und laden das Dataset:
# Import required libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.tokenize import RegexpTokenizer
import numpy as np
from sklearn import preprocessing
from scipy.sparse import hstack
import pandas_profiling
# Load dataset
df = pd.read_csv("tvshow.csv")
df=df.iloc[:,1:] # removing in unnamed index column
df.head()
| Unnamed: 0 | ID | Title | Year | Age | IMDb | Rotten Tomatoes | Netflix | Hulu | Prime Video | Disney+ | Type | Directors | Genres | Country | Language | Runtime | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | Inception | 2010 | 13+ | 8.8 | 87% | 1 | 0 | 0 | 0 | 0 | Christopher Nolan | Action,Adventure,Sci-Fi,Thriller | United States,United Kingdom | English,Japanese,French | 148.0 |
| 1 | 1 | 2 | The Matrix | 1999 | 18+ | 8.7 | 87% | 1 | 0 | 0 | 0 | 0 | Lana Wachowski,Lilly Wachowski | Action,Sci-Fi | United States | English | 136.0 |
| 2 | 2 | 3 | Avengers: Infinity War | 2018 | 13+ | 8.5 | 84% | 1 | 0 | 0 | 0 | 0 | Anthony Russo,Joe Russo | Action,Adventure,Sci-Fi | United States | English | 149.0 |
| 3 | 3 | 4 | Back to the Future | 1985 | 7+ | 8.5 | 96% | 1 | 0 | 0 | 0 | 0 | Robert Zemeckis | Adventure,Comedy,Sci-Fi | United States | English | 116.0 |
| 4 | 4 | 5 | The Good, the Bad and the Ugly | 1966 | 18+ | 8.8 | 97% | 1 | 0 | 1 | 0 | 0 | Sergio Leone | Western | Italy,Spain,West Germany | Italian | 161.0 |
# Show initial information about the dataset
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 16744 entries, 0 to 16743
Data columns (total 16 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ID 16744 non-null object
1 Title 16744 non-null object
2 Year 16744 non-null int64
3 Age 7354 non-null object
4 IMDb 16173 non-null float64
5 Rotten Tomatoes 5158 non-null object
6 Netflix 16744 non-null int64
7 Hulu 16744 non-null int64
8 Prime Video 16744 non-null int64
9 Disney+ 16744 non-null int64
10 Type 16744 non-null int64
11 Directors 16018 non-null object
12 Genres 16469 non-null object
13 Country 16309 non-null object
14 Language 16145 non-null object
15 Runtime 16152 non-null float64
dtypes: float64(2), int64(6), object(8)
memory usage: 2.0+ MB
df.Type.unique()
array([0])
Mit fehlenden Werten arbeiten
In diesem Abschnitt arbeitest du mit fehlenden Werten über die Funktion isnull(). Ein Beispiel siehst du hier:
#Finding Missing values in all columns
miss = pd.DataFrame(df.isnull().sum())
miss = miss.rename(columns={0:"miss_count"})
miss["miss_%"] = (miss.miss_count/len(df.ID))*100
miss
| misscount | miss% | |
|---|---|---|
| ID | 0 | 0.000000 |
| Title | 0 | 0.000000 |
| Year | 0 | 0.000000 |
| Age | 9390 | 56.079790 |
| IMDb | 571 | 3.410177 |
| Rotten Tomatoes | 11586 | 69.194935 |
| Netflix | 0 | 0.000000 |
| Hulu | 0 | 0.000000 |
| Prime Video | 0 | 0.000000 |
| Disney+ | 0 | 0.000000 |
| Type | 0 | 0.000000 |
| Directors | 726 | 4.335882 |
| Genres | 275 | 1.642379 |
| Country | 435 | 2.597946 |
| Language | 599 | 3.577401 |
| Runtime | 592 | 3.535595 |
Du siehst, dass die Variablen Age und Rotten Tomatoes jeweils über 50 % fehlende Werte haben – ein Warnsignal.
So gehst du mit den fehlenden Werten um:
- Spalten mit mehr als 50 % fehlenden Werten entfernen
- Fehlende Werte in IMDb, Directors, Genres, Country, Language und Runtime entfernen
- Index zurücksetzen
- Year in den Datentyp object umwandeln
# Dropping values with missing % more than 50%
df.drop(['Rotten Tomatoes', 'Age'], axis = 1, inplace=True)
# Dropping Na's from the following columns
df.dropna(subset=['IMDb','Directors', 'Genres', 'Country', 'Language', 'Runtime'],inplace=True)
df.reset_index(inplace=True,drop=True)
# Converting into object type
df.Year = df.Year.astype("object")
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 15233 entries, 0 to 15232
Data columns (total 14 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ID 15233 non-null object
1 Title 15233 non-null object
2 Year 15233 non-null object
3 IMDb 15233 non-null float64
4 Netflix 15233 non-null int64
5 Hulu 15233 non-null int64
6 Prime Video 15233 non-null int64
7 Disney+ 15233 non-null int64
8 Type 15233 non-null int64
9 Directors 15233 non-null object
10 Genres 15233 non-null object
11 Country 15233 non-null object
12 Language 15233 non-null object
13 Runtime 15233 non-null float64
dtypes: float64(2), int64(5), object(7)
memory usage: 1.6+ MB
Verteilungsdiagramme
Die Verteilung der Spalte Year kannst du mit der distplot()-Funktion von seaborn prüfen. Zeichnen wir die Verteilung der Erscheinungsjahre.
#checking Distribution of years
plt.figure(figsize=(20,5))
sns.distplot(df['Year'])
plt.show()

Das Diagramm zeigt die Verteilung der Produktionsjahre. Man erkennt, dass der Großteil der Filme zwischen 2000 und 2020 entstanden ist. Zeichnen wir nun die Verteilung der IMDb-Bewertungen.
# Distribution of IMDb Rating
plt.figure(figsize=(20,5))
sns.distplot(df['IMDb'])
plt.show()

Die Verteilung ist leicht schief. Du kannst daraus ableiten, dass der durchschnittliche IMDb-Wert vieler Filme bei etwa 6,5 liegt.
Als Nächstes die Verteilung der Laufzeiten.
# Distribution of runtime
sns.distplot(df['Runtime'])
plt.show()

Daraus lässt sich schließen, dass die durchschnittliche Laufzeit der Filme zwischen 80 und 120 Minuten liegt.
Verteilung der Filme je Streaming-Plattform
In diesem Abschnitt betrachtest du die filmbezogene Verteilung je Plattform. Zuerst erstellst du eine Funktion m_cnt(), die die Anzahl der Filme pro Plattform zählt. Anschließend kannst du die Anteile per Kreisdiagramm visualisieren.
# A function to calculate the movies in different Streaming platforms
def m_cnt(plat, count=False):
if count==False:
print('Platform {} Count: {}'. format(plat, df[plat].sum()))
else:
return df[plat].sum()
# Let's see count of movies/shows of each streaming platform
m_cnt('Netflix')
m_cnt('Hulu')
m_cnt('Prime Video')
m_cnt('Disney+')
Platform Netflix Count: 3152
Platform Hulu Count: 848
Platform Prime Video Count: 11289
Platform Disney+ Count: 542
# Movies on each platform
lab = 'Prime Video','Netflix', 'Hulu', 'Disney'
s = [m_cnt('Prime Video', count=True),
m_cnt('Netflix', count=True),
m_cnt('Hulu', count=True),
m_cnt('Disney+', count=True)]
explode = (0.1, 0.1, 0.1, 0.1)
#plotting
fig1, ax1 = plt.subplots()
ax1.pie(s,
labels = lab,
autopct = '%1.1f%%',
explode = explode,
shadow = True,
startangle = 100)
ax1.axis = ('equal')
plt.show()

Das Diagramm zeigt: Prime Video hostet die meisten Titel mit 71 % Anteil, Netflix etwa 20 %. Disney+ und Hulu liegen mit 5,4 % bzw. 3,4 % deutlich darunter.
Filmverteilung nach Genre
Hier siehst du die Verteilung nach Genres. Zunächst bereitest du die Daten auf: Mehrere Genres in einer Zelle müssen aufgetrennt werden. Dafür nutzt du split(), apply() und stack(). split() trennt durch Kommas, apply(pd.Series,1) erzeugt mehrere Spalten pro Genre, und stack() stapelt sie wieder zu einer Spalte.
Nach diesen Schritten wird eine neue Genres-Spalte mit dem bestehenden DataFrame verbunden und du kannst plotten. Mit value_counts() zeigst du die Top-Genres samt Anzahl, und mit der pandas-plot()-Funktion visualisierst du sie.
##split the genres by ',' & then stack it one after the other for easy analysis.
g = df['Genres'].str.split(',').apply(pd.Series, 1).stack()
g.index = g.index.droplevel(-1)
# Assign name to column
g.name = 'Genres'
# delete column
del df['Genres']
# join new column with the existing dataframe
df_genres = df.join(g)
# Count of movies according to genre
plt.figure(figsize=(15,5))
sns.countplot(x='Genres', data=df_genres)
plt.xticks(rotation=90)
plt.show()

Hier wird deutlich: Drama und Comedy sind die am häufigsten vertretenen Genres.
Filmverteilung nach Ländern
Nun die Verteilung nach Ländern. Mehrere Länder in einer Zelle behandelst du wie bei den Genres mit split(), apply() und stack(). split() trennt kommagetrennte Werte, apply(pd.Series,1) erzeugt mehrere Spalten, stack() führt sie in einer Spalte zusammen.
Nach diesen drei Operationen wird die neue Country-Spalte angefügt und du bist bereit für die Visualisierung. Die Top 10 Länder mit Filmanzahl zeigst du per value_counts() und pandas-plot().
# Split the Country by ',' & then stack it one after the other for easy analysis.
c = df['Country'].str.split(',').apply(pd.Series, 1).stack()
c.index = c.index.droplevel(-1)
# Assign name to column
c.name = 'Country'
# delete column
del df['Country']
# join new column with the existing dataframe
df_country = df.join(c)
# plotting top 10 country and movie count
df_country['Country'].value_counts()[:10].plot(kind='bar',figsize=(15,5))
plt.show()

Die Grafik zeigt: Die meisten Filme stammen aus den Vereinigten Staaten.
Filmverteilung nach Sprachen
Als Nächstes die Verteilung nach Sprachen. Mehrere Sprachen in einer Zelle behandelst du ebenfalls mit split(), apply() und stack(). split() erzeugt Listen, apply(pd.Series,1) verteilt sie auf Spalten, stack() führt sie wieder in einer Spalte zusammen.
Nach diesen Schritten wird die neue Language-Spalte angefügt und du kannst die Top 10 Sprachen mit value_counts() und der pandas-plot()-Funktion visualisieren.
# perform stacking operation on language column
l = df['Language'].str.split(',').apply(pd.Series,1).stack()
l.index = l.index.droplevel(-1)
# Assign name to column
l.name = 'Language'
# delete column
del df['Language']
# join new column with the existing dataframe
df_language = df.join(l)
# plotting top 10 Language and movie count
df_language['Language'].value_counts()[:10].plot(kind='bar',figsize=(15,3))
plt.show()

Die meisten Filme sind auf Englisch.
Verteilung der IMDb-Bewertungen je Plattform
Hier visualisierst du die IMDb-Bewertungsverteilung pro Plattform. Dafür nutzt du die melt()-Funktion und ein FacetGrid. melt() wandelt einen breiten DataFrame in ein langes Format um. Ein Beispiel:
# melting platform columns to create visualization
df2 = pd.melt(df, id_vars=["ID","Title","Year","IMDb","Type","Runtime"], var_name="platform")
df2 = df2[df2.value==1]
df2.drop(columns=["value"],axis=1,inplace=True)
# Distribution of IMDB rating in different platform
g = sns.FacetGrid(df2, col = "platform")
g.map(plt.hist, "IMDb")
plt.show()

Die Darstellung zeigt die durchschnittliche IMDb-Verteilung auf jeder Plattform.

Laufzeit je Plattform nach Altersgruppe
Im Abschnitt „Mit fehlenden Werten arbeiten" haben wir die Spalte Age entfernt. Um die Laufzeit je Plattform nach Altersgruppen zu analysieren, lädst du die Daten erneut und wendest melt() an.
# Load dataset
df = pd.read_csv("tvshow.csv")
df=df.iloc[:,1:]
df.ID = df.ID.astype("object")
# melting platform columns to create visualization
df2 = pd.melt(df, id_vars=["ID","Title","Year","Age","IMDb","Rotten Tomatoes","Type","Runtime"], var_name="platform")
df2 = df2[df2.value==1]
df2.drop(columns=["value"],axis=1,inplace=True)
Nach dem erneuten Laden und dem Melting erstellst du den Plot für Laufzeit vs. Plattform nach Altersgruppen.
# Total of runtime in different platform
ax = sns.barplot(x="platform", y="Runtime",hue="Age", estimator=sum, data=df2)

Der Plot zeigt: Die Gesamtlaufzeit der Inhalte auf Prime Video ist bei der Altersgruppe 18+ deutlich höher als auf allen anderen Plattformen. Daraus lässt sich ableiten, dass ein Großteil des Prime-Video-Angebots auf 18+ ausgerichtet ist.
Ein Empfehlungssystem bauen
In den letzten Jahren haben Empfehlungsdienste bei YouTube, Walmart, Netflix und vielen anderen Web-Services enorme Wirkung entfaltet. Von Produktempfehlungen über werbebasierte Monetarisierung bis hin zum passgenauen Matching von Relevanz und Präferenzen: Empfehlungssysteme sind aus unserem Weballtag nicht wegzudenken.
Im Kern sind es mathematische Frameworks, die Nutzerinnen und Nutzern passende Produkte oder Dienste vorschlagen – je nach Bedarf und Interesse, etwa Filme zum Anschauen, Artikel zum Lesen, Produkte zum Kaufen oder Musik zum Hören. Grundsätzlich gibt es drei Ansätze, um Empfehlungssysteme zu bauen:
- Content-based-Methoden: Erstellen ein Modell für Nutzer oder Items anhand der Item-Merkmale und empfehlen ähnliche Items.
- Collaborative-Filtering-Methoden: Modellieren Ähnlichkeiten zwischen Nutzer-Item-Interaktionen oder zwischen Nutzern zur Item-Empfehlung.
- Hybride Methoden: Kombinieren Content- und Collaborative-Ansätze für bessere Ergebnisse.
Bereiten wir das Dataset für das Empfehlungssystem vor. Zuerst prüfst du die fehlenden Werte:
# Reading Data Again
df = pd.read_csv("tvshow.csv")
df=df.iloc[:,1:]
#Finding Missing values in all columns
miss = pd.DataFrame(df.isnull().sum())
miss = miss.rename(columns={0:"miss_count"})
miss["miss_%"] = (miss.miss_count/len(df.ID))*100
miss
#Dropping values with missing % more than 50%
df.drop(['Rotten Tomatoes', 'Age'], axis = 1, inplace=True)
# Dropping Na's from the following columns
df.dropna(subset=['IMDb','Directors', 'Genres', 'Country', 'Language', 'Runtime'],inplace=True)
df.reset_index(inplace=True,drop=True)
# converting into object type
df.ID = df.ID.astype("object")
df.Year = df.Year.astype("object")
Du baust zwei Empfehlungssysteme auf Basis der Kosinusähnlichkeit.
1. Nur mit numerischen Variablen
2. Mit numerischen und kategorialen Variablen
Nur numerische Spalten verwenden
Schritt 1: Numerische Variablen auswählen
ndf = df.select_dtypes(include=['float64',"int64"])
Schritt 2: Numerische Variablen mit Min-Max-Scaler skalieren, um Komplexität und Trainingszeit zu reduzieren
#importing minmax scaler
from sklearn import preprocessing
# Create MinMaxScaler Object
scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
# Create dataframe after transformation
ndfmx = pd.DataFrame((scaler.fit_transform(ndf)))
# assign column names
ndfmx.columns=ndf.columns
# Show initial 5 records
ndfmx.head()
Schritt 3: Ähnlichkeitsscore per Kosinusähnlichkeit berechnen
Jetzt berechnest du den Ähnlichkeitsscore mit der Kosinusähnlichkeit.
# Import cosine similarity
from sklearn.metrics.pairwise import cosine_similarity
# Compute the cosine similarity
sig = cosine_similarity(ndfmx, ndfmx)
# Reverse mapping of indices and movie titles
indices = pd.Series(df.index, index=df['Title']).drop_duplicates()
indices.head()
Schritt 4: Funktion schreiben, um Empfehlungen auf Basis des Ähnlichkeitsscores zu erhalten
- Die Funktion nimmt Filmtitel und Ähnlichkeitsscores entgegen. Sie findet den Index des Titels in unserer Indizes-Serie.
- Sie ermittelt die paarweisen Ähnlichkeitsscores aller Filme.
- Sie sortiert die Scores absteigend und konvertiert sie in eine Liste.
- Sie greift die Top-10-Scores und -Indizes ab und gibt die Titel dieser Filme zurück.
def give_rec(title, sig=sig):
# Get the index corresponding to original_title
idx = indices[title]
# Get the pairwise similarity scores
sig_scores = list(enumerate(sig[idx]))
# Sort the movies
sig_scores = sorted(sig_scores, key=lambda x: x[1], reverse=True)
# Scores of the 10 most similar movies
sig_scores = sig_scores[1:11]
# Movie indices
movie_indices = [i[0] for i in sig_scores]
# Top 10 most similar movies
return df['Title'].iloc[movie_indices]
# Execute get_rec() function for getting recommendation
give_rec("The Matrix",sig = sig)
Die Empfehlungen sind hier noch nicht überzeugend. Der Grund: Es fließen nur Bewertungen, Laufzeiten und Plattform-Variablen ein. Mit zusätzlichen Informationen wie Genre, Regie und Land lässt sich das verbessern.
Numerische und textuelle Spalten verwenden
Das erste System funktioniert, aber die Empfehlungen sind nicht optimal. Daher probierst du einen besseren Ansatz aus.
df.head()
Schritt 1:
Du führst die Textspalten in einer Spalte zusammen, tokenisierst und nutzt einen TF-IDF-Vektorisierer, um eine Sparse-Matrix der TF-IDF-Scores zu erstellen. Dann wählst und skalierst du numerische Variablen und fügst sie der Sparse-Matrix hinzu. Die Vorverarbeitung umfasst:
- Alle Spalten mit Datentyp object auswählen und in einer Liste speichern.
- ID und Title entfernen.
- Alle Text-/Object-Spalten mit Kommas zu einer Spalte verbinden.
- Einen Tokenizer erstellen, um unerwünschte Elemente wie Symbole und Zahlen zu entfernen.
- TF-IDF-Vektoren aus dem Text erzeugen. Danach die numerischen Spalten vorverarbeiten.
- Numerische Variablen in ein DataFrame übernehmen.
- Numerische Variablen per Min-Max-Scaler auf den Bereich 0–1 skalieren.
- Numerische Variablen per
hstackzur TF-IDF-Sparse-Matrix hinzufügen.
#the function performs all the important preprocessing steps
def preprocess(df):
#combining all text columns
# Selecting all object data type and storing them in list
s = list(df.select_dtypes(include=['object']).columns)
# Removing ID and Title column
s.remove("Title")
s.remove("ID")
# Joining all text/object columns using commas into a single column
df['all_text']= df[s].apply(lambda x: ','.join(x.dropna().astype(str)),axis=1)
# Creating a tokenizer to remove unwanted elements from our data like symbols and numbers
token = RegexpTokenizer(r'[a-zA-Z]+')
# Converting TfidfVector from the text
cv = TfidfVectorizer(lowercase=True,stop_words='english',ngram_range = (1,1),tokenizer = token.tokenize)
text_counts= cv.fit_transform(df['all_text'])
# Aselecting numerical variables
ndf = df.select_dtypes(include=['float64',"int64"])
# Scaling Numerical variables
scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
# Applying scaler on our data and converting i into a data frame
ndfmx = pd.DataFrame((scaler.fit_transform(ndf)))
ndfmx.columns=ndf.columns
# Adding our adding numerical variables in the TF-IDF vector
IMDb = ndfmx.IMDb.values[:, None]
X_train_dtm = hstack((text_counts, IMDb))
Netflix = ndfmx.Netflix.values[:, None]
X_train_dtm = hstack((X_train_dtm, Netflix))
Hulu = ndfmx.Hulu.values[:, None]
X_train_dtm = hstack((X_train_dtm, Hulu))
Prime = ndfmx["Prime Video"].values[:, None]
X_train_dtm = hstack((X_train_dtm, Prime))
Disney = ndfmx["Disney+"].values[:, None]
X_train_dtm = hstack((X_train_dtm, Disney))
Runtime = ndfmx.Runtime.values[:, None]
X_train_dtm = hstack((X_train_dtm, Runtime))
return X_train_dtm
Schritt 2: Funktion anwenden und Sparse-Matrix erzeugen
# Preprocessing data
mat =preprocess(df)
mat.shape
Schritt 3: Erneut Kosinusähnlichkeit berechnen
# using cosine similarity
from sklearn.metrics.pairwise import cosine_similarity
# Compute the sigmoid kernel
sig2 = cosine_similarity(mat, mat)
# Reverse mapping of indices and movie titles
indices = pd.Series(df.index, index=df['Title']).drop_duplicates()
Schritt 4: Empfehlungen aus dem verbesserten System abrufen
give_rec("The Matrix",sig=sig2)
Diesmal liefert das System deutlich bessere Treffer. Das zeigt: Durch relevante Zusatzinformationen wie Beschreibungstext lässt sich ein content-basiertes Empfehlungssystem spürbar verbessern.
Fazit
Glückwunsch – du hast das Ende dieses Tutorials erreicht!
Du hast eine explorative Analyse des Film-Datasets für Streaming-Plattformen durchgeführt: fehlende Werte untersucht, individuelle Verteilungen geplottet und die Verteilung der Filme je Plattform betrachtet. Außerdem hast du Einblicke zu Genres, Ländern, Sprachen, IMDb-Bewertungen und Laufzeiten gewonnen. Zum Schluss hast du gesehen, wie man in Python ein Empfehlungssystem aufbaut.
Schau dir auch DataCamps Tutorial Recommender Systems in Python an.
