...a jiné metody průzkumu dat a redukce počtu dimenzí
Co se naučíte¶
Analýza hlavních komponent (anglicky “principal component analysis” neboli PCA) je metoda na snížení počtu dimenzí mnohorozměrných dat. To se může hodit z hlediska vizualizace nebo kvůli úspoře času/paměti při strojovém učení.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
%matplotlib inline
sns.set_style('whitegrid')
Jak porovnat babičce víno ve spíži?¶
Data níže obsahují 13 různých charakteristik 178 italských vín. Představte si, že chcete tato vína vykreslit jako body do jednoho grafu. Případně si představte, že vás babička poprosila, abyste je vyrovnali do polic tak, aby “podobná” vína byla u sebe a “různá” vína co nejdál od sebe. Jak něco takového uděláte?
from sklearn.datasets import load_wine
wine = load_wine()
wine_df = pd.DataFrame(wine.data, columns=wine.feature_names)
print(wine_df.shape)
wine_df.head()
Jedna možnost je vybrat si dvojici dvou vlastností, která je zásadní pro vaše data. Ale co když se ve víně nevyznáte? Můžete pochopitelně vykreslit grafy všech možných dvojic.
sns.pairplot(wine_df);

plt.figure(figsize=(20,10))
sns.heatmap(wine_df.corr(), annot=True);

Jak vidíme, mnoho charakteristik je korelovaných (např. total_phenols
a flavanoids
). PCA umožňuje převést mnoho charakteristik (sloupců) na několik málo, které a) jsou nezávislé b) zachycují maximum informace (variability) v datech.
Protože obrazovky a stěny ve spíži obvykle bývají dvourozměrné, budeme dále (nebude-li řečeno jinak) zjednodušovat z X charakteristik na dvě, abychom tyto potom mohli zakreslit do grafu na osu a . Podívejme se, jak to dopadne pro náši datovou sadu vín.
PCA graf¶
# Tento blok znormalizuje sloupečky v tabulce, aby měly průměr nula a rozptyl jedna
# Pokud to neuděláte, bude hrát roli v jakých jednotkách slooupec je
scaler = StandardScaler()
wine_df_scaled = scaler.fit_transform(wine_df)
# Tento blok spočte PCA souřadnice (volíme zjednodušení na dvě komponenty)
pca = PCA(n_components=2)
wine_pca = pd.DataFrame(pca.fit_transform(wine_df_scaled), columns=['PCA1', 'PCA2'])
wine_pca
# A tento je zakreslí do grafu
sns.scatterplot(x=wine_pca.PCA1, y=wine_pca.PCA2);
/home/petr/.pyenv/versions/3.8.1/envs/pca/lib/python3.8/site-packages/seaborn/_decorators.py:36: FutureWarning: Pass the following variable as a keyword arg: x. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
warnings.warn(

Splnili jsme úkol, bude babička spokojená? Jsou podobná vína zakreslená poblíž sebe?
K vínům lze v naší databázi dohledat ještě jednu informaci - vína patří do jedné ze tří kategorií (barva vína?). A skutečně, pokud si body na PCA grafu obarvíme podle kategorie vína, jasně vidíme tři shluky.
sns.scatterplot(x=wine_pca.PCA1, y=wine_pca.PCA2, hue=wine.target_names[wine.target]);
/home/petr/.pyenv/versions/3.8.1/envs/pca/lib/python3.8/site-packages/seaborn/_decorators.py:36: FutureWarning: Pass the following variable as a keyword arg: x. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
warnings.warn(

Podíl zachycené variability¶
PCA umožňuje též kvantifikovat, kolik procent variability je v prvních dvou PCA komponentách zachyceno.
pca.explained_variance_ratio_
array([0.36198848, 0.1920749 ])
Cvičení 1: Tučňáci¶
Pro první cvičení použijeme data o tučňácích od Allison Horst, https://
Podobně jako slavná data o kosatcích od R. A. Fishera se tučňáčí data snaží být jednoduchým učebnicovým příkladem na zvládnutí základních technik manipulace s daty a visualizace.
penguins = pd.read_csv('penguins_size_nona.csv')
print(penguins.shape)
penguins.head()
scaler = StandardScaler()
penguins_df_scaled = pd.DataFrame(scaler.fit_transform(penguins[['culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'body_mass_g']]),
columns=penguins.columns[3:])
penguins_df_scaled.head()
sns.heatmap(penguins_df_scaled.corr(), annot=True);


Zredukujte tyto čtyři charakteristiky na dvě PCA, spočtěte množství vysvětlené variability a vykreslete vše do scatterplotu, který obarvíte podle druhu tučňáka. Je na PCA grafu vidět rozdíl mezi druhy tučňáků? Je vidět rozdíl mezi samcemi a samicemi?
# sem napište řešení
pca.explained_variance_ratio_
array([0.36198848, 0.1920749 ])
Breast Cancer Wisconsin Dataset¶
Data z klinických měření ohledně rakoviny prsu z Wisconsinské univerzity. Celkem máme 569 pacientů a u každého z nich 31 veličin ohodnocujících FNA snímek prsní tkáně a informaci, zda-li byl nádor zhoubný (maligní) či nezhoubný (benigní).
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
col_names = list(cancer.feature_names)
col_names.append('target')
cancer_df = pd.DataFrame(np.c_[cancer.data, cancer.target], columns=col_names)
print(cancer_df.shape)
print(cancer.target_names)
cancer_df.head()
Začněme stejně jako v minulém případě pohledem párovými grafy, které si tentokrát obarvíme dle typu nádoru.
sns.pairplot(cancer_df, hue='target', vars=['mean radius', 'mean texture', 'mean perimeter', 'mean area',
'mean smoothness', 'mean compactness', 'mean concavity',
'mean concave points', 'mean symmetry', 'mean fractal dimension']);

plt.figure(figsize=(20,10))
sns.heatmap(cancer_df.drop(columns="target").corr(), annot=True);

Na první pohled vidíme, že některé charakteristiky nesou více informace než jiné.
plt.figure(figsize=(10, 8))
sns.scatterplot(x = 'mean area', y = 'mean smoothness', hue = 'target', data = cancer_df)

Spočítejme tedy nyní PCA komponenty, jejich podíl na variabilitě a nakresleme PCA graf.
scaler = StandardScaler()
scaler.fit(cancer_df.drop('target', axis=1))
scaled_data = scaler.transform(cancer_df.drop('target', axis=1))
pd.DataFrame(scaled_data).head()
pca = PCA(n_components=2)
cancer_pca = pd.DataFrame(pca.fit_transform(scaled_data), columns=['PCA1', 'PCA2'])
cancer_pca
pca.explained_variance_ratio_
array([0.44272026, 0.18971182])
plt.figure(figsize=(13,7))
sns.scatterplot(x=cancer_pca.PCA1, y=cancer_pca.PCA2,
hue=cancer.target_names[cancer.target],
)
plt.xlabel('PCA1 (44%)')
plt.ylabel('PCA2 (19%)');
/home/petr/.pyenv/versions/3.8.1/envs/pca/lib/python3.8/site-packages/seaborn/_decorators.py:36: FutureWarning: Pass the following variable as a keyword arg: x. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
warnings.warn(

Na PCA grafu je vidět několik oranžových (benigních) teček uprostřed modré (maligní) oblasti. Možná by bylo dobré podívat se na tyto pacienty podrobněji. Ale na jak zjistíme, kolikátému řádku bod odpovídá? Potřebujeme interaktivní graf!
Interaktivní PCA graf¶
import plotly
import plotly.express as px
# V našich datech není Patient ID, tak si nějaké vytvoříme
cancer_df['patient'] = ['Patient' + str(i) for i in range(cancer_df.shape[0])]
cancer_df['patient']
0 Patient0
1 Patient1
2 Patient2
3 Patient3
4 Patient4
...
564 Patient564
565 Patient565
566 Patient566
567 Patient567
568 Patient568
Name: patient, Length: 569, dtype: object
# naucse.python.cz neumí zobrazit interaktivní graf, proto je níže zakomentovaný
fig = px.scatter(x=cancer_pca.PCA1, y=cancer_pca.PCA2, color=cancer.target_names[cancer.target],
hover_name = cancer_df['patient'])
# fig.show() # zrušte komentář pro nakreslení obrázku
Interpretace koeficientů¶
Aniž bychom příliš zabíhali do matematické podstaty PCA, každá naše uměle vytvořená komponenta je ve skutečnosti lineární kombinací jendotlivých charakteristik (po normalizaci), tedy např.
Pokud jsme sloupce normalizovali pomocí StandardScaler
, můžeme velikost koeficientů (v absolutní hodnotě) do jisté míry interpretovat jako jak moc se daná vlastnost na PCA komponentě podílí. Buďte však v této interpretaci opatrní (je snadné vidět v datech něco, co tam ve skutečnosti není).
loadings = pd.DataFrame(pca.components_.T, index=cancer_df.columns[:-2], columns=cancer_pca.columns)
loadings.sort_values('PCA1', ascending=False).head(n=10)
U části kódu v této části jsme se inspirovali v https://
Cvičení 2: Vliv stárnutí na expresi proteinů v ledvinách¶
Pro druhé cvičení využijeme měření exprese proteinů z myších ledvin. Celkem máme 188 myší, které se liší pohlavím (samci, samice), zařazením do skupiny (generace, G8-G12) a věkem (6, 12 či 18 měsíců). Více informací o experimentu naleznete na https://
Na základě proteinové exprese nakreslete PCA graf a rozhodněte, jestli první dvě komponenty odpovídají nějaké informaci, kterou v záznamech máte (pohlaví, skupina, věk). Pokud vám zbyde čas, pokuste se nakreslit interaktivní graf.
url_protein_expr = 'http://github.com/simecek/naucse.python.cz/blob/master/lessons/pydata/pca/static/protein_expr.zip?raw=true'
protein_expr = pd.read_csv(url_protein_expr, compression="zip")
print(protein_expr.shape)
protein_expr.head()
# sem napište řešení
Na závěr si ukažme dva trochu zvláštnější případy použití:
Číslice¶
Ručně psaných 1797 číslic od 43 lidí, zmenšených do rastru 8x8, intenzita jasu od 0 do 16. Stejně jako většina našich dat pochází z UCI ML Repository.
Podívejme se na jednu konkrétní číslici, jak vypadá její číselná reprezentace a její obrázek.
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
digits = load_digits()
#velikost dat
print(digits.data.shape)
# 42. číslice jako list
print(digits.images[42])
# 42. číslice jako obrázek
plt.gray()
plt.matshow(digits.images[42].reshape(8,8));
(1797, 64)
[[ 0. 0. 0. 0. 12. 5. 0. 0.]
[ 0. 0. 0. 2. 16. 12. 0. 0.]
[ 0. 0. 1. 12. 16. 11. 0. 0.]
[ 0. 2. 12. 16. 16. 10. 0. 0.]
[ 0. 6. 11. 5. 15. 6. 0. 0.]
[ 0. 0. 0. 1. 16. 9. 0. 0.]
[ 0. 0. 0. 2. 16. 11. 0. 0.]
[ 0. 0. 0. 3. 16. 8. 0. 0.]]
<Figure size 432x288 with 0 Axes>

Data si převedeme ta tabulku o 1797 řádcích a 64 sloupcích (a ověříme si, že 42. řádek je správně).
digits.images[42].reshape(1,64)
array([[ 0., 0., 0., 0., 12., 5., 0., 0., 0., 0., 0., 2., 16.,
12., 0., 0., 0., 0., 1., 12., 16., 11., 0., 0., 0., 2.,
12., 16., 16., 10., 0., 0., 0., 6., 11., 5., 15., 6., 0.,
0., 0., 0., 0., 1., 16., 9., 0., 0., 0., 0., 0., 2.,
16., 11., 0., 0., 0., 0., 0., 3., 16., 8., 0., 0.]])
digits_df = digits.images.reshape(1797,64)
print(digits_df.shape)
digits_df[42,:]
(1797, 64)
array([ 0., 0., 0., 0., 12., 5., 0., 0., 0., 0., 0., 2., 16.,
12., 0., 0., 0., 0., 1., 12., 16., 11., 0., 0., 0., 2.,
12., 16., 16., 10., 0., 0., 0., 6., 11., 5., 15., 6., 0.,
0., 0., 0., 0., 1., 16., 9., 0., 0., 0., 0., 0., 2.,
16., 11., 0., 0., 0., 0., 0., 3., 16., 8., 0., 0.])
A nakreslíme si klasický PCA graf z prvních dvou komponent (povšimněte si, že nezávisí na tom, kolik n_components
zvolíme).
# zkuste změnit počet komponent n_components a podívejte se, co se stane
n_components = 5
pca = PCA(n_components=n_components)
column_names = ["PCA" + str(i) for i in range(1,n_components+1)]
pca.fit(digits.images.reshape(1797,64))
digits_pca = pd.DataFrame(pca.transform(digits_df), columns=column_names)
digits_pca
plt.figure(figsize=(10,10))
sns.scatterplot(x=digits_pca.PCA1, y=digits_pca.PCA2,
hue=digits.target_names[digits.target], legend='full',
palette=sns.color_palette());
/home/petr/.pyenv/versions/3.8.1/envs/pca/lib/python3.8/site-packages/seaborn/_decorators.py:36: FutureWarning:
Pass the following variable as a keyword arg: x. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.

Inverzní transformace¶
PCA můžeme chápat i jako formu komprese dat, pro převod z prostoru PCA zpět slouží metoda inverse_transform
. Zkuste měnit počet komponent a dívejte se, jak se mění podoba rekonstruované číslice 1.
digits_from_pca = pca.inverse_transform(digits_pca)
digits_from_pca
array([[ 1.18974834e-15, 9.71423065e-02, 4.96370054e+00, ...,
4.68963092e+00, -2.32562004e-01, -5.71321254e-01],
[-8.91793934e-16, 2.42372419e-01, 2.85356300e+00, ...,
7.78522071e+00, 3.50042876e+00, 1.06361335e+00],
[-6.06035520e-16, -1.63415273e-01, 8.23932800e-01, ...,
9.49245715e+00, 3.62264911e+00, 3.48060610e-01],
...,
[-5.85855079e-17, 9.65723722e-02, 2.27239217e+00, ...,
8.21481537e+00, 3.24594200e+00, 5.81401047e-01],
[ 7.19943186e-16, -7.15894319e-03, 3.50952907e+00, ...,
8.95909763e+00, 1.39503123e+00, 2.26457051e-01],
[ 3.61296989e-16, 2.41846392e-01, 4.68685352e+00, ...,
1.29149736e+01, 4.82448953e+00, 8.15606554e-01]])
pca.explained_variance_ratio_, pca.explained_variance_ratio_.sum()
(array([0.14890594, 0.13618771, 0.11794594, 0.08409979, 0.05782415]),
0.5449635266933277)
plt.matshow(digits_from_pca[42].reshape(8,8));

t-SNE¶
t-SNE (T-distributed Stochastic Neighbor Embedding) je jiná technika, jak obrazit mnohorozměrná data do dvou či tří dimenzí.
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, random_state=0)
digits_tsne = pd.DataFrame(tsne.fit_transform(digits_df), columns=['TSNE1', 'TSNE2'])
plt.figure(figsize=(10,10))
sns.scatterplot(x=digits_tsne.TSNE1, y=digits_tsne.TSNE2,
hue=digits.target_names[digits.target], legend='full',
palette=sns.color_palette());

Filmová hodnocení¶
Všimněte si, že celou dnešní lekci jsme pracovali s daty, které byly na PCA jako dělaná. Co ale dělat, když budou data např. obsahovat chybějící hodnoty?
Problém si demonstrujeme na hodnocení 50 filmů 100 diváky, která jsou vybraná z databáze Movie Lens.
url_ratings = 'http://github.com/simecek/naucse.python.cz/blob/master/lessons/pydata/pca/static/ML_small.csv?raw=true'
ratings = pd.read_csv(url_ratings)
print(ratings.shape)
ratings.head()
scaler = StandardScaler()
ratings_scaled = scaler.fit_transform(ratings.drop(columns='title'))
Pravděpodobnostní PCA¶
Pravděpodobnostní (Probabilistic) PCA nám umožňuje doplnit chybějící pozorování. V Pythonu je implementovaná v knihovně PyPPCA.
# pip install PyPPCA # odstraňte komentář pro instalaci balíčku
# bohužel nefunguje ve Windows
from pyppca import ppca
# značení podle ppca
C, ss, M, X, Ye = ppca(ratings_scaled, 2, False)
plt.figure(figsize=(15,15))
ax = sns.scatterplot(x=X[:,0], y=X[:,1])
for title, x, y in zip(ratings.title.values, X[:,0], X[:,1]):
ax.text(x, y, title, size='medium', color='black')
/home/petr/.pyenv/versions/3.8.1/envs/pca/lib/python3.8/site-packages/seaborn/_decorators.py:36: FutureWarning:
Pass the following variable as a keyword arg: x. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.

Doplnění (imputation) chybějících hodnot¶
Jinou možností je pokusit se chybějící hodnoty doplnit. Knihovna sklearn
nám za tímto účelem nabízí funkce SimpleImputer
a IterativeImputer
.
# from sklearn.experimental import enable_iterative_imputer # uncomment for iterative imputer
# from sklearn.impute import IterativeImputer
# imp = IterativeImputer(missing_values=np.nan, max_iter=20, random_state=0)
from sklearn.impute import SimpleImputer
imp = SimpleImputer(missing_values=np.nan, strategy='mean')
ratings_imp = imp.fit_transform(ratings_scaled)
pd.DataFrame(ratings_imp, columns=ratings.columns[1:], index=ratings.title).head()
pca = PCA(n_components=2)
ratings_pca = pd.DataFrame(pca.fit_transform(ratings_imp), columns=['PCA1', 'PCA2'])
plt.figure(figsize=(15,15))
ax = sns.scatterplot(x=ratings_pca.PCA1, y=-ratings_pca.PCA2)
for title, x, y in zip(ratings.title.values, ratings_pca.PCA1, -ratings_pca.PCA2):
ax.text(x, y, title, size='medium', color='black')

Hierarchické shlukování (clustering) a heatmapy¶
PCA graf není jediná metoda, jak zachytit souvislosti mezi jednotlivými pozorováními mnohorozměrných dat. Asi nejčastější alternativou je heatmapa doplněná dendrogramy hierarchického shlukování tak, jak ji můžete vidět na obrázku níže.
Povšimněte si, že podobně jako u PCA vidíme tři jasné shluky vzorků vín.
sns.clustermap(pd.DataFrame(wine_df_scaled, columns=wine_df.columns).T);

Shrnutí a závěrečné poznámky¶
V tomto notebooku jsme se naučili aplikovat na mnohorozměrná data Analýzu hlavních komponent (Principal Component Analysis, PCA) za účelem grafické reprezentace nebo snížení velikosti dat (pro zrychlení výpočtu či ušetření paměti).
Témata PCA příbuzná, která se sem už nevešla:
- Linear Discriminant Analysis technika příbuzná PCA, kde maximalizuje nikolik množství vysvětlené variability, ale množství informace vzhledem k jiné (závisle) proměnné
- Correspondence Analysis technika podobná PCA, ale aplikovaná na kategoriální, nikoli spojitá data
- Faktorová analýza: metoda používaná především v psychologii a psychometrii, matematický model jiný, ale též pomáhá odhalovat faktory za mnohorozměrnými daty
- Biplot PCA graf, kde krom pozorování jsou zanesené i vektory charakteristik (často používaný v R)
- Dvou nebo tří rozměrná PCA? U interaktivního grafu může být někdy dobrý nápad nakreslit 3-rozměrnou PCA. V opačném případě může 3-rozměrný graf “dobře vypadat”, ale jinak pootočením ztrácíte informaci.
- Nespecifické filtrování charakteristik: pokud víte, že velká část vašich sloupců je konstantních, irelevantních apod. může být dobrý nápad je před PCA odfiltrovat (např. podle rozptylu)
- Pokud chcete vědět víc o PCA, přečtěte si příslušnou kapitolu z Data Science Handbook
- Zmínili jsme hierarchické clusterování (shlukování), ale už nám nezbyl čas na k-Means clustering
- A pokud byste potřebovali vysvětlit PCA svojí babičce, podívejte se na CrossValidated.