Esittelen tässä Jupyter-muistiossa Python-ohjelmointikielen data-analyysityökaluja. Muistion esimerkit pohjautuvat Facebook-ryhmässä järjestettyyn musiikkikilpailuun, joka on mukailtu televisio-ohjelmasta Levyraati.
Aineisto on kerätty Facebook-ryhmästä keväällä 2016. Mukana olleet raatilaiset ovat arvostelleet jokaisen kappaleen, kirjoittaneen sanallisen arvioinnin sekä antaneet numeerisen arvosanan. Tässä analyysissä hyödynnän vain arvosanoja.
# Data-analyysin perustyökalut
import numpy as np
import pandas as pd
# Kuvaajat
%matplotlib inline
import seaborn as sns
sns.set_style("whitegrid")
df = pd.read_csv('levyraati-2016.csv', index_col=0, decimal=',')
df
Sami | Joe | Mikko | Taneli | Arttu | Ville | Kirmo | |
---|---|---|---|---|---|---|---|
Kappale | |||||||
Tungevaag & Raaban – Parade | 4.50 | 0.50 | 3 | 2 | 2.0 | 2.00 | 4.50 |
Asa – Mä haluun olla hippi | 1.00 | 2.25 | 4 | 1 | 2.0 | 2.00 | 2.50 |
Chubby Wolf – You are the Description that brings me out of Myself | 0.50 | 1.00 | 1 | 3 | 4.0 | 1.00 | 2.00 |
Supperheads – Easy | 2.00 | 2.75 | 5 | 3 | 2.0 | 5.00 | 3.50 |
Dream Theater – The Silent Man | 2.25 | 2.25 | 1 | 3 | 2.5 | 4.50 | 3.75 |
Tony Bennett & Lady Gaga – It Don't Mean a Thing | 3.00 | 1.25 | 3 | 3 | 4.0 | 1.50 | 4.00 |
Elina Born – Miss Calculation | 2.00 | 2.50 | 4 | 4 | 1.0 | 3.00 | 3.75 |
Caravan Palace – Lone Digger | 0.50 | 2.50 | 5 | 5 | 4.0 | 1.75 | 4.75 |
Avril Lavigne – When You're Gone | 4.50 | 2.75 | 2 | 3 | 1.0 | 3.75 | 4.25 |
Halestorm – Here's to US | 3.50 | 4.00 | 4 | 4 | 0.5 | 3.50 | 4.00 |
Levyraadin voittaa eniten pisteitä kerännyt kilpailija. Raadissamme oli kaksi tasavertaista kappaletta: Here's to US ja Lone Digger.
df.sum(axis=1).sort_values().plot(kind = 'barh', title = 'Yhteenlasketut pisteet suurimmasta pienimpään')
<matplotlib.axes._subplots.AxesSubplot at 0x7f2af6cc3da0>
Seuraavaksi selvitän, kuinka yksimielisesti raati äänesti. Aloitan piirtämällä laatikkodiagrammin, joka paljastaa kappaleiden yksittäiset arvosanat, vaihteluvälin, mediaanin sekä ylä- ja alaneljänneksen.
Voin nähdä kuvioista, että kappaleiden pisteytyksessä on suurta vaihtelua. Tämä viittaa siihen, että raati ei ole arvostelussaan yksimielinen. Lisäksi huomaan, että tiettyjen kappaleiden laatikot ovat muita pidempiä, eli nämä kappaleet saavat ristiriitaisia arvosteluja.
ax = sns.boxplot(data=df.T, orient='h')
ax = sns.swarmplot(data=df.T, orient='h', color='black')
ax.set_title('Kappaleiden arvosanajakauma')
<matplotlib.text.Text at 0x7f2af4946748>
Pystyn jo kuvaa katsomalla arvailla, mikä kappaleista on saanut ristiriitaisimman vastaanoton, mutta haluan todeta asian laskennallisesti. Käytän tarkoitukseeni standardipoikkeamaa eli keskimääräistä poikkeamaa odotusarvosta. $$D(X)=\sigma_x = \sqrt{\sigma^{2}_x}$$
Kappaleen Lone Digger arvosanat poikkeavat 1,8 pistettä odotusarvosta. Levyraadin ääniharava on myös kierroksen ristiriitaisin kappale. Pienin keskihajonta on Asan kappaleella Mä haluun olla hippi, eli tuomaristo on yksimielisesti pitänyt sita raadin heikoimpiin kuuluvana esityksenä.
pd.DataFrame(df.std(axis=1).sort_values(ascending=False), columns=['Keskihajonta'])
Keskihajonta | |
---|---|
Kappale | |
Caravan Palace – Lone Digger | 1.790351 |
Tungevaag & Raaban – Parade | 1.463850 |
Chubby Wolf – You are the Description that brings me out of Myself | 1.286375 |
Halestorm – Here's to US | 1.281740 |
Supperheads – Easy | 1.264205 |
Avril Lavigne – When You're Gone | 1.253566 |
Dream Theater – The Silent Man | 1.136515 |
Elina Born – Miss Calculation | 1.135205 |
Tony Bennett & Lady Gaga – It Don't Mean a Thing | 1.086990 |
Asa – Mä haluun olla hippi | 1.019162 |
Edellisessä tarkastelussa huomasin, että sama kappale saa vaihtelevia pisteitä eri arvostelijoilta. Seuraavaksi haluan selvittää, miten raadit jäsenet eroavat toisistaan. Aloitan laskemalla, mitä raatilaiset antavat keskimäärin arvosanaksi kappaleille.
df.mean().sort_values().plot(kind='barh')
<matplotlib.axes._subplots.AxesSubplot at 0x7f2af482b710>
Keskiarvoista näkee, että Kirmo on antaa keskimäärin parhaimmat pisteet ja Joe huonoimmat. Ero voi johtua siitä, että Kirmo piti tämän kierroksen kappaleista enemmän kuin Joe tai sitten he käyttävät arvosteluasteikkoa eri tavalla.
Arvosana 3 voi olla yhdelle jäsenelle hyvä ja toiselle huono. Tuomaristo käyttää arvosteluasteikkoa eri tavoin, joten pisteet eivät välttämättä ole vertailukelpoisia. Haluan, että lukuja on helppo vertailla keskenään, joten normalisoin luvut. Normalisoinnin jälkeen jälkeen arvosanat ovat nollan ja yhden välillä.
$$X' = \frac{X - X_{min}}{X_{max} - X_{min}}$$# Määritellään kaavan mukainen normalisointifunktio
def normalize_values(x):
return( (x - x.min()) / (x.max() - x.min()) )
df_norm = df.apply(normalize_values)
df_norm
Sami | Joe | Mikko | Taneli | Arttu | Ville | Kirmo | |
---|---|---|---|---|---|---|---|
Kappale | |||||||
Tungevaag & Raaban – Parade | 1.0000 | 0.000000 | 0.50 | 0.25 | 0.428571 | 0.2500 | 0.909091 |
Asa – Mä haluun olla hippi | 0.1250 | 0.500000 | 0.75 | 0.00 | 0.428571 | 0.2500 | 0.181818 |
Chubby Wolf – You are the Description that brings me out of Myself | 0.0000 | 0.142857 | 0.00 | 0.50 | 1.000000 | 0.0000 | 0.000000 |
Supperheads – Easy | 0.3750 | 0.642857 | 1.00 | 0.50 | 0.428571 | 1.0000 | 0.545455 |
Dream Theater – The Silent Man | 0.4375 | 0.500000 | 0.00 | 0.50 | 0.571429 | 0.8750 | 0.636364 |
Tony Bennett & Lady Gaga – It Don't Mean a Thing | 0.6250 | 0.214286 | 0.50 | 0.50 | 1.000000 | 0.1250 | 0.727273 |
Elina Born – Miss Calculation | 0.3750 | 0.571429 | 0.75 | 0.75 | 0.142857 | 0.5000 | 0.636364 |
Caravan Palace – Lone Digger | 0.0000 | 0.571429 | 1.00 | 1.00 | 1.000000 | 0.1875 | 1.000000 |
Avril Lavigne – When You're Gone | 1.0000 | 0.642857 | 0.25 | 0.50 | 0.142857 | 0.6875 | 0.818182 |
Halestorm – Here's to US | 0.7500 | 1.000000 | 0.75 | 0.75 | 0.000000 | 0.6250 | 0.727273 |
Normalisoinnin jälkeen laskemme voittajan uudelleen. Jos kaikki raatilaiset olisivat käyttäneet arvosteluasteikkoa yhtä laajasti, niin kilpailun olisi voittanut kappale Lone Digger.
df_norm.T.sum().sort_values().plot(kind='barh')
<matplotlib.axes._subplots.AxesSubplot at 0x7f2af47aff28>
Levyraadin pistetaulukko kuvastaa raatilaisten mieltymyksiä, joten voin selvittää kenen musiikkimaku on lähimpänä omaani. Aion verrata yksitellen jokaisen raatilaisen antamia pisteitä omiini ja katsoa, kuinka paljon pisteissä on eroa. Raatilainen, jonka pisteissä on vähiten eroa omiini, on mahdollisesti musiikkimaultaan samantyylinen kuin minä.
Kahden tasolla olevan pisteen etäisyyden voi laskea Pythagoraan lauseella. Samaa menettelyä voi soveltaa myös moniulotteisemmissa avaruuksissa. Menetelmän nimi on euklidinen mittaus:
$$ \mathrm{d}(\mathbf{p},\mathbf{q}) = \sqrt{\sum_{i=1}^n (q_i-p_i)^2} $$Raatimme arvosteli 10 kappaletta, joten arvioin yhden raatilaisen etäisyyden toiseen raatilaiseen 10 kappaleen perusteella. Sovellamme siis euklidista mittausta 10-ulotteisessa avaruudessa.
# Numpy-moodulin funktio linalg.norm, laskee euklidisen etäisyyden kahden vektorin välillä.
# Haluan verrata datataulun jokaista saraketta jokaiseen sarakkeeseen, joten luon silmukka-
# rakenteen kahdella sisäkkäisellä lamda-funktiolla.
D = df_norm.apply(lambda c1: df_norm.apply(lambda c2: np.linalg.norm(c1 - c2)))
# Ei verrata raatilaisen musiikkimakua häneen itseensä <-- arvoksi NaN
np.fill_diagonal(D.values, np.nan)
D.style.background_gradient()
Sami | Joe | Mikko | Taneli | Arttu | Ville | Kirmo | |
---|---|---|---|---|---|---|---|
Sami | nan | 1.40019 | 1.71505 | 1.5013 | 1.98273 | 1.25468 | 1.09199 |
Joe | 1.40019 | nan | 1.10657 | 0.916821 | 1.79142 | 0.846568 | 1.23788 |
Mikko | 1.71505 | 1.10657 | nan | 1.19896 | 1.71874 | 1.46575 | 1.22052 |
Taneli | 1.5013 | 0.916821 | 1.19896 | nan | 1.33583 | 1.27169 | 0.951022 |
Arttu | 1.98273 | 1.79142 | 1.71874 | 1.33583 | nan | 1.92952 | 1.61635 |
Ville | 1.25468 | 0.846568 | 1.46575 | 1.27169 | 1.92952 | nan | 1.33103 |
Kirmo | 1.09199 | 1.23788 | 1.22052 | 0.951022 | 1.61635 | 1.33103 | nan |
Kahden nimen risteyskohta kertoo raatilaisten etäisyyden toisistaan. Suuri luku tarkoittaa suurta eroa annetuissa arvosanoissa. Huomaan, että nimeni sarakkeella pienin arvo on Joen kohdalla (1,10657), eli hänen musiikkimakunsa eroaa vähiten omastani. Vähiten samanlainen musiikkimaku minulla on Artun kanssa (1,71874).
Yritän tehdä taulukosta helppolukuisemman korvaamalla euklidisen etäisyyden sijoitusnumerolla. Toisin sanoen haluan nähdä helposti, kenen musiikkimaku on minua ensimmäiseksi, toiseksi ja kolmanneksi lähimpänä.
D.rank().style.background_gradient()
Sami | Joe | Mikko | Taneli | Arttu | Ville | Kirmo | |
---|---|---|---|---|---|---|---|
Sami | nan | 5 | 5 | 6 | 6 | 2 | 2 |
Joe | 3 | nan | 1 | 1 | 4 | 1 | 4 |
Mikko | 5 | 3 | nan | 3 | 3 | 5 | 3 |
Taneli | 4 | 2 | 2 | nan | 1 | 3 | 1 |
Arttu | 6 | 6 | 6 | 5 | nan | 6 | 6 |
Ville | 2 | 1 | 4 | 4 | 5 | nan | 5 |
Kirmo | 1 | 4 | 3 | 2 | 2 | 4 | nan |
Nyt voin etsiä nimeni pystysarakkeesta ja näen riveistä, kenen musiikkimaku on lähimpänä minua. Rivitasolla näen, kuinka mones olen muiden raatilaisten sijoituslistalla. Parhaimmillaan musiikkimakuni on kolmanneksi lähimpänä toisia raatilaisia.
Taulukosta nähdään, että Joen musiikkimaku on lähimpänä useimpia raatilaisia, sillä hänellä on eniten 1-sijoituksia. Poikkeuksellisin musiikkimaku on Artulla, joka on kauimpana useimpia raatilaisia.
Tasapaksuja arvosanoja jakava Joe on lähempänä muita raatilaisia kuin ääripään arvosanoja jakava Arttu. Tämä johtuu euklidisen mittaustavasta, jossa erotusten summa korotetaan neliöön: yksi iso ero voi rangaista enemmän kuin monta pientä.
Kokeilen vielä euklidisen mittauksen lisäksi toista tapaa etsiä musiikkimaultaan lähin raatilainen. Sijoitan raatilaiset pisteinä kolmiulotteiseen kuvaajaan, josta etäisyyden voi arvioida silmämääräisesti. Tällä hetkellä yhdellä raatilaisella on 10 kappaletta tai ominaisuutta, jotka joudun typistämään kolmeksi. Tarkoitukseni on niputtaa kymmenen tasoa kolmeksi tasoksi siten, että mahdollisimman vähän tietoa menisi hukkaan.
Vähennän dimensioita pääkomponenttianalyysillä, joka etsii tasoja, joiden varianssi on mahdollisimman suuri. Toisin sanoen pääkomponenttianalyysi pyrkii keksimään ne tasot, jotka ovat kaikkein ilmaisuvoimaisimpia.
from IPython.display import YouTubeVideo
YouTubeVideo("_UVHneBUBW0")
# Käytän SciKit Learnin funktiota PCA, joka osaa redusoida dimensioita
from sklearn.decomposition import PCA
pca = PCA(n_components=3)
X = pca.fit_transform(df_norm.T)
# Piirrän kolmiulotteisen kuvaajan Matplotlib-kirjastolla
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(12,10))
ax = fig.add_subplot(111, projection='3d')
# Lisään kuvaajan redusoidut dimensiot
ax.scatter(X[:,0], X[:,1], X[:,2])
# Nimeän pisteet raatilaisten mukaan
for i, name in enumerate(df_norm.columns):
ax.text(X[i,0], X[i,1], X[i,2], name)
# Hyvä kuvakulma
ax.azim = 135