Skip to content
Snippets Groups Projects
Commit 2e17c716 authored by Christof Kaufmann's avatar Christof Kaufmann
Browse files

Notebooks from applied-cs/data-science@53ecbb5b

parent 09325418
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id:0010-892a41e9d42a613c5d49687ab7fd7d9bc14e580682d5ac0fc4b218440d1 tags:
# The Simpsons
Sie finden die folgenden Dateien anbei:
- `simpsons_char.csv`: Zuordnung IDs und Namen der Charaktere
- `simpsons_ep-char.csv`: Zuordnung IDs der Episoden zur ID der
Charaktere, die in dieser Episode auftreten
Erstellen Sie einen Graph mit folgenden Eigenschaften:
- Die Nodes sollen den Charakteren entsprechen
- Die Edges stellen dar, ob zwei Charaktere in derselben Episode
vorkomen
*Bonus: Behandeln Sie Charaktere, die doppelt vorkommen. Die meisten
doppelten Charaktere haben denselben Namen, z. B. `'Matt Groening'`.
Diese Fälle sind egal, weil wir nur mit den Namen arbeiten, aber es gibt
auch andere Fälle. Wir haben folgende spezielle Fälle gefunden, bei
denen beide Varianten jeweils einmal vorkommen. Diese können wir folgt
vereint werden:*
- `'Dr. Velimorovic'``'Dr. Velimirovic'`
- `'Frank "Grimey" Grimes'``'Frank Grimes'`
- `'*NSYNC'``"'N Sync"`
Reguläre Aufgaben:
1. Dabei sollen die Gewichte der Edges der Anzahl Episoden entsprechen,
in denen die Charaktere gemeinsam vorkamen.
2. Wenden Sie anschließend die Funktion
[`pagerank`](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.link_analysis.pagerank_alg.pagerank.html)
an um die wichtigsten Personen zu berechnen.
3. Plotten Sie den Graphen (bzw. einen Teil davon) und machen Sie dabei
die Wichtigkeit sichtbar (Größe oder Farbe der Nodes).
## Lösung
Als erstes laden wir die Namen:
%% Cell type:code id:0011-cb5719a5cfc5ceccde6943dcfd2e01f201f66c1e7c69348340ecffedbe6 tags:
```
import numpy as np
import pandas as pd
import networkx as nx
names = pd.read_csv('simpsons_char.csv', index_col='id').squeeze()
```
%% Cell type:markdown id:0012-6e080fdd60b7116b3de71b04e25b8dcc4e45b84b61725677bc1499a726d tags:
Bevor wir zur Bonusaufgabe kommen, schauen wir uns etwas viel
schwierigeres an. Exkurs: Wie findet man eigentlich die drei genannten
Spezialfälle? Vollautomatisch hat das nicht funktioniert. Einen Fall
habe ich durch Zufall in den Daten gesehen: *Frank “Grimey" Grimes* und
*Frank Grimes*. Um die anderen zu finden, kann man ähnliche Namen in
Beziehung setzen und sich anschauen, wo das noch auftritt. Aber was
heißt hier ähnlich? In
[`textdistance`](https://github.com/life4/textdistance) gibt es viele
Algorithmen um Ähnlichkeiten bzw. Unterschiede in Strings zu finden.
Welche Metrik ist hier passend? Das lässt sich vielleicht am besten mit
einem Beispiel herausfinden. Also definieren wir folgende Variablen als
Testfall:
%% Cell type:code id:0013-b12bb61380f8d029c3d002facff2f0dc38c5a1a7abe82a4b0345f5bf681 tags:
```
f1 = "Frank \"Grimey\" Grimes"
f2 = "Frank Grimes"
f3 = 'Frank Gehry'
```
%% Cell type:markdown id:0014-b89b487b80c67db0be39b13f7257362aa6e002c4aadb348ea98473a9a34 tags:
Nun suchen wir eine Metrik, bei der `f1` näher an `f2` als `f1` an `f3`
und `f2` an `f3` ist.
%% Cell type:code id:0015-3e07d58be181736f28076ceb01494387dcaf80ecccc4c48047d62b99763 tags:
```
import textdistance
all_alg_names = ['Hamming', 'MLIPNS', 'Levenshtein', 'DamerauLevenshtein', 'JaroWinkler', 'StrCmp95', 'NeedlemanWunsch', 'Gotoh', 'SmithWaterman', 'Jaccard', 'Sorensen', 'Tversky', 'Overlap', 'Tanimoto', 'Cosine', 'MongeElkan', 'Bag', 'LCSSeq', 'LCSStr', 'RatcliffObershelp', 'ArithNCD', 'RLENCD', 'BWTRLENCD', 'SqrtNCD', 'EntropyNCD', 'BZ2NCD', 'LZMANCD', 'ZLIBNCD', 'MRA', 'Editex']
for alg_name in all_alg_names:
alg = getattr(textdistance, alg_name)()
d12 = alg.normalized_distance(f1, f2)
d13 = alg.normalized_distance(f1, f3)
d23 = alg.normalized_distance(f2, f3)
if d12 < d13 and d12 < d23:
print(f'{alg_name}: {d12:.3f} < {d13:.3f}, {d23:.3f}')
```
%% Output
JaroWinkler: 0.086 < 0.143, 0.109
StrCmp95: 0.086 < 0.143, 0.098
Gotoh: 0.175 < 0.382, 0.227
SmithWaterman: 0.417 < 0.909, 0.455
Overlap: 0.000 < 0.091, 0.182
RatcliffObershelp: 0.273 < 0.438, 0.304
BWTRLENCD: 0.727 < 0.955, 0.923
SqrtNCD: 0.553 < 0.637, 0.568
EntropyNCD: 0.041 < 0.090, 0.066
BZ2NCD: 0.186 < 0.279, 0.216
MRA: 0.000 < 0.500, 0.500
%% Cell type:markdown id:0016-e8abea3ccb872cfc9b5a77397c1936e4ec7c3d3d80fe6a1a21f571ac7df tags:
Wir entscheiden uns für
[Ratcliff-Obershelp](https://de.wikipedia.org/wiki/Gestalt_Pattern_Matching),
obwohl [Overlap](https://en.wikipedia.org/wiki/Overlap_coefficient)
vielleicht auch passend für unseren Zweck und sogar kommutativ wäre. Um
die Distanzen kommutativ zu machen, nutzen wir einfach
$\hat d(a, b) = \frac{d(a, b) + d(b, a)}{2}$. Wir vergleichen die Namen
als Kleinbuchstaben, was in diesem Fall das Problem zwischen `"*NSYNC"`
und `"'N Sync"` löst. Um die paarweise Distanzmatrix zu berechnen,
benutzen wir Funktionen aus `scipy`. Zuerst verwenden wir `pdist`, was
quasi die untere Dreiecksmatrix der paarweisen Distanzmatrix berechnet
und als 1D-Array zurückgibt. Anschließend benutzen wir `squareform`, was
daraus eine quadratische, symmetrische Matrix macht. Auf der Diagonale
stehen dann allerdings 0en, weil $d(a, a) = 0$ per Definition einer
Distanzmetrik. Weil diese uns aber nicht interessieren, setzen wir die
Diagonaleinträge auf 100, während alle anderen Einträge in \[0, 1\]
liegen. Ausgehend von dem Abstand `d12`, begrenzen wir den Radius der
Nachbarn auf 0.3 und hoffen, dass alle relevanten Duplikate ähnlich
genug sind. Mit Hilfe der Nachbarn können wir nachher im Graphen
Verbindungen darstellen.
%% Cell type:code id:0017-0ee3182140f3ed5b2c2257ee8d03877714fc42af640b5897f1ac3b708a0 tags:
```
import numpy as np
from scipy.spatial.distance import pdist, squareform
from sklearn.neighbors import NearestNeighbors
import textdistance
alg = textdistance.RatcliffObershelp().normalized_distance
# alg = textdistance.Overlap().normalized_distance
# RatcliffObershelp ist nicht kommutativ, also nehmen wir den Mittelwert aus d(a, b) und d(b, a)
def commutative_alg(a, b):
return 0.5 * (alg(a[0], b[0]) + alg(b[0], a[0]))
dist_mat = squareform(pdist(pd.DataFrame(names.str.lower()), metric=commutative_alg))
np.fill_diagonal(dist_mat, 100) # np.inf does not work
nn = NearestNeighbors(metric='precomputed', radius=0.30)
nn.fit(dist_mat)
dists, neigh = nn.radius_neighbors(dist_mat)
```
%% Cell type:markdown id:0018-9bf3389087ef02a8cce9bb091ea27fa9c8b08cda0b212541a15aac8a816 tags:
Das wollen wir nun plotten. Der erste Ansatz könnte sein per PCA bzw.
MDS die Namen als 2D-Scatter-Plot darzustellen und zu schauen, welche am
nächsten liegen. Das ginge so:
%% Cell type:code id:0019-91d25a0df398214cb0b5123453256eaba55a7f76bada2e3f8c756512e9a tags:
```
from sklearn.manifold import MDS
has_neigh = [n.size > 0 for n in neigh]
mds = MDS(dissimilarity='precomputed', normalized_stress='auto')
name_map = mds.fit_transform(dist_mat[np.ix_(has_neigh, has_neigh)])
name_map = pd.DataFrame(name_map, columns=['x', 'y'], index=names.index[has_neigh])
name_map['name'] = names[has_neigh]
```
%% Cell type:markdown id:0020-e9e15400554005850fac11d25146041aae8e05fb6b36160e10a865ad97f tags:
Und dann könnte man diese mittels Plotly Express plotten:
%% Cell type:code id:0021-e795b7e06079e52fe0ca1b1938a79bc462d7f276c24ceb8fc6665cee9e3 tags:
```
import plotly.express as px
px.scatter(name_map.reset_index(), x='x', y='y', text='name', hover_data='id')
```
%% Cell type:markdown id:0022-92795f67d006660c130838ffc57f03ce2505c3851cd3b74747ad4dcb2d3 tags:
Allerdings funktioniert das irgendwie nicht gut. Der zweite Ansatz, das
als Graph zu plotten ist viel besser – auch weil dort die Nachbarn
miteinander verbunden sind. Zunächst erstellen wir den Graphen:
%% Cell type:code id:0023-e9659a0861fd2244c6810c059d71cec09ad96576b54b5a9555208ba3f20 tags:
```
G = nx.Graph()
for i, name1 in enumerate(names):
for d, j in zip(dists[i], neigh[i]):
name2 = names.iloc[j]
G.add_edge(name1, name2, weight=1-d)
```
%% Cell type:markdown id:0024-6af90f2217e9ffbf961c47fefed13ae4a09a25f11eb8a99e1b621c181d6 tags:
Dann plotten wir ihn interaktiv mittels `pyvis`:
%% Cell type:code id:0025-5fb248386fb43e45ad670e889309d3d4324d5d010112ab0d32f02fabc41 tags:
```
from pyvis.network import Network
nt = Network(width='100%', height='900px', notebook=False, directed=False, cdn_resources='in_line')
nt.from_nx(G.copy())
for attr in nt.edges:
attr['width'] = 5 * attr['width'] ** 2
nt.toggle_physics(True)
# net.prep_notebook()
nt.show('nx.html')
```
%% Cell type:markdown id:0027-c6b466ca093ebb4f132e0bbf54e5db7f0d83c71feab315c65898aade6d2 tags:
Hier können wir die Spezialfälle bei den Duplikaten gut identifizieren
und recherchieren. Wir finden beispielsweise auch *Principal Seymour
Skinner* und *The Real Principal Seymour Skinner*, aber das sind
tatsächlich zwei verschiedene Personen, siehe
[Wikipedia](https://en.wikipedia.org/wiki/The_Principal_and_the_Pauper).
Mit Overlap und einen Radius von 0.05 bekommt man übrigens eine
vollkommen andere, aber für unseren Fall weniger nützliche Struktur.
Das wars mit dem Exkurs. Wenn wir die Spezialfälle einmal haben, ist es
sehr leicht, diese zu de-duplizieren (**Bonusaufgabe**). Zur Sicherheit
überprüfen wir an Hand von *Dr. Velimirovic*, dass es vor der
De-Duplikation zwei Schreibweisen und nachher nur noch eine gibt:
%% Cell type:code id:0028-b114c9510bb06ea3598a489c9743e2973fb914f393c3fc1a60049b60f9d tags:
```
assert names.str.startswith('Dr. Velimi').sum() == 1, "Start with original data!"
assert names.str.startswith('Dr. Velimo').sum() == 1, "Start with original data!"
bad = ['Frank "Grimey" Grimes', '*NSYNC', 'Dr. Velimorovic']
good = ['Frank Grimes', "'N Sync", 'Dr. Velimirovic']
bad_names = names.isin(bad)
names[bad_names] = good
assert names.str.startswith('Dr. Velimi').sum() == 2, "Mapping did not work."
assert names.str.startswith('Dr. Velimo').sum() == 0, "Mapping did not work."
```
%% Cell type:markdown id:0029-81f2788d57a6288f51f70ef29993f094e7ace21a3538a552ab6f55cde53 tags:
Zurück zu den regulären Aufgaben. Nun laden wir die Verknüpfung der
Episoden mit den Charakteren als IDs und fügen mittels `merge` die
entsprechenden Namen hinzu.
%% Cell type:code id:0030-1c0bed94a8102f4a947c0e48019e57a2ad110d811a8e4ff0d5ec4748aae tags:
```
episodes = pd.read_csv('simpsons_ep-char.csv')
episodes = episodes.merge(names, left_on='character_id', right_index=True).drop(columns='character_id').sort_values(by='episode_id')
```
%% Cell type:markdown id:0031-873f03a11adbcd50d51ae59be19301e259fa794cf430204297c75ba9065 tags:
Damit können wir nun den Graphen bauen, der die Verbindungen der
Charaktere zeigt. Dabei initialisieren wir die Gewichte der Edges mit 0
und inkrementieren es für jede Episode, in der die Charaktere gemeinsam
vorkommen.
%% Cell type:code id:0032-07d141eaf767d8b2c276cf8287f415513de7163dd4638e7ec7392378450 tags:
```
G = nx.Graph()
for eid, chars in episodes.groupby('episode_id'):
char_names = chars['char_name'].unique()
for i, c1 in enumerate(char_names):
for c2 in char_names[i + 1:]:
G.add_edge(c1, c2, color={'opacity': 0.7, 'inherit': 'both'})
if 'weight' not in G[c1][c2]:
G[c1][c2]['weight'] = 0
G[c1][c2]['weight'] += 1
```
%% Cell type:markdown id:0033-27389ba88740762cb4de3ddfc46140e3dedad2f15030eb2fba02ca6acc1 tags:
Nun ermitteln wir den Pagerank der Nodes und verwenden es als Größe.
%% Cell type:code id:0034-74e5fba0f5ad615fccc5252c56564770108460608feedae959e427b4390 tags:
```
pr = nx.pagerank(G)
node_size = {n: max(120 * v, 3) for n, v in pr.items()}
nx.set_node_attributes(G, node_size, 'size')
```
%% Cell type:markdown id:0035-675b9374e72271a687a3328cabb0912268b8442f6b2305fdc670f1f3527 tags:
Das zu plotten wäre vielleicht etwas zu um die Struktur zu erkennen und
es lädt sowieso recht langsam. Daher filtern wir die Edges mit einem
Gewicht weniger als oder gleich 3 raus und Entfernen Nodes, die dadurch
gar keine Verbindung mehr haben:
%% Cell type:code id:0036-4c71dd7ebd5282604305c18a502c01d6766fbc275e57d95fc6593a88443 tags:
```
for c1, c2, attr in list(G.edges(data=True)):
if attr['weight'] <= 3:
G.remove_edge(c1, c2)
if G.degree[c1] <= 0:
G.remove_node(c1)
if G.degree[c2] <= 0:
G.remove_node(c2)
```
%% Cell type:markdown id:0037-424b0b6ed665314f818fba0944a659099a646f3fd1f8998e647a29117a3 tags:
Nun plotten wir das Ergebnis:
%% Cell type:code id:0038-6c4bc1929ab7cb3703c58b917e6f3765ff725e0ff5ce79002ede71d57f0 tags:
```
from pyvis.network import Network
net = Network(width='100%', height='900px', notebook=False, directed=False, select_menu=True, cdn_resources='in_line')
# net.show_buttons(True)
net.from_nx(G.copy())
for attr in net.edges:
attr['width'] = np.log(attr['width'])
net.repulsion()
# net.prep_notebook()
net.show('nx.html')
```
%% Cell type:markdown id:0039-d5843a2f8728f26f9d0b401b11f41ab96aa55a908601151b7f065a9b2c7 tags:
Für das Rendering im Jupyter-Notebook, muss man evtl. noch
`notebook=True` setzen und `net.prep_notebook()` dekommentieren.
%% Cell type:markdown id:0006-4dba46341fe8d0ab170c3957b9535b44dc02b59210bfddbad257c824e76 tags: %% Cell type:markdown id:0009-6efc9b991ed2829202bdd132227fd5376987e98f56cc6aad907f00b375c tags:
# The Simpsons # The Simpsons
Sie finden die folgenden Dateien anbei: Sie finden die folgenden Dateien anbei:
- `simpsons_char.csv`: Zuordnung ID und Name der Charaktere - `simpsons_char.csv`: Zuordnung IDs und Namen der Charaktere
- `simpsons_ep-char.csv`: Zuordnung ID der Episode zur ID der - `simpsons_ep-char.csv`: Zuordnung IDs der Episoden zur ID der
Charaktere, die in dieser Sendung auftreten Charaktere, die in dieser Episode auftreten
Erstellen Sie einen Graph mit folgenden Eigenschaften: Erstellen Sie einen Graph mit folgenden Eigenschaften:
- Die Nodes sollen den Charakteren entsprechen - Die Nodes sollen den Charakteren entsprechen
- Die Edges stellen dar, ob zwei Charaktere in derselben Episode - Die Edges stellen dar, ob zwei Charaktere in derselben Episode
vorkomen vorkomen
1. Dabei sollen die Gewichte der Edges der Anzahl Sendungen *Bonus: Behandeln Sie Charaktere, die doppelt vorkommen. Die meisten
entsprechen, in denen die Charaktere gemeinsam vorkamen. doppelten Charaktere haben denselben Namen, z. B. `'Matt Groening'`.
Diese Fälle sind egal, weil wir nur mit den Namen arbeiten, aber es gibt
auch andere Fälle. Wir haben folgende spezielle Fälle gefunden, bei
denen beide Varianten jeweils einmal vorkommen. Diese können wir folgt
vereint werden:*
- `'Dr. Velimorovic'``'Dr. Velimirovic'`
- `'Frank "Grimey" Grimes'``'Frank Grimes'`
- `'*NSYNC'``"'N Sync"`
Reguläre Aufgaben:
1. Dabei sollen die Gewichte der Edges der Anzahl Episoden entsprechen,
in denen die Charaktere gemeinsam vorkamen.
2. Wenden Sie anschließend die Funktion 2. Wenden Sie anschließend die Funktion
[`pagerank`](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.link_analysis.pagerank_alg.pagerank.html) [`pagerank`](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.link_analysis.pagerank_alg.pagerank.html)
an um die wichtigsten Personen zu berechnen. an um die wichtigsten Personen zu berechnen.
3. Plotten Sie den Graphen (bzw. einen Teil davon) und machen Sie dabei 3. Plotten Sie den Graphen (bzw. einen Teil davon) und machen Sie dabei
die Wichtigkeit sichtbar (Größe oder Farbe der Nodes). die Wichtigkeit sichtbar (Größe oder Farbe der Nodes).
Hier Ihr Code: Hier Ihr Code:
%% Cell type:code id:0007-44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 tags: %% Cell type:code id:0010-44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 tags:
``` ```
``` ```
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment