from datetime import datetime
print(f'Päivitetty {datetime.now().date()} / Aki Taanila')
Päivitetty 2023-12-11 / Aki Taanila
Oletan, että muistion https://nbviewer.org/github/taanila/kuvaileva/blob/main/kuvaileva.ipynb sisältö on lukijalle tuttu.
Menetelmät:
Tässä muistiossa käytän laskentaan seuraavia funktioita:
Tässä muistiossa käytän seuraavia funktioita kaavioiden luontiin:
plot(kind='bar') pystypylväskaavio
plot(kind='barh') vaakapylväskaavio
histplot seaborn-kirjaston histogrammi, joka esittää luokitellun jakauman
barplot seaborn-kirjaston pylväskaavio, joka esittää oletuksena keskiarvot
boxplot seaborn-kirjaston boxplot, joka esittää viiden luvun yhteenvedon
scatterplot seaborn-kirjaston hajontakaavio
jointplot seaborn-kirjaston hajontakaavio terästettynä reunajakaumilla
heatmap seaborn-kirjaston värikartta korrelaatioiden visualisointiin
pairplot seaborn-kirjaston usean hajontakaavion kokoelma
displot seaborn-kirjaston usean kaavion kokoelma (histplot)
catplot seaborn-kirjaston usean kaavion kokoelma (countplot, barplot, boxplot)
relplot seaborn-kirjaston usean kaavion kokoelma (scatterplot).
Tilastollisella merkitsevyystestauksella testaan onko otoksessa havaittu ero/riippuvuus tilastollisesti merkitsevää (yleistettävissä otoksesta laajempaan joukkoon, josta otos on poimittu). Tilastollisiin merkitsevyyden testauksiin käytän:
Jos merkitsevyystestaus tuntuu käsittämättömältä, niin lue artikkelini p-arvosta https://tilastoapu.wordpress.com/2012/02/14/p-arvo/
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
# Merkitsevyystestaukseen
from scipy.stats import chi2_contingency, ttest_ind, f_oneway, pearsonr
# Kaavioiden prosenttiakselin muotoiluun
from matplotlib.ticker import PercentFormatter
ticks = PercentFormatter(xmax=100, decimals=0, symbol=' %')
df = pd.read_excel('https://taanila.fi/data1.xlsx')
# Muuttujien tekstimuotoisia arvoja
sukup = ['mies', 'nainen']
koulutus = ['peruskoulu', '2. aste', 'korkeakoulu', 'ylempi korkeakoulu']
tyytyväisyys = ['erittäin tyytymätön', 'tyytymätön', 'siltä väliltä', 'tyytyväinen', 'erittäin tyytyväinen']
# Luokkarajat iän luokitteluun
bins = [20, 30, 40, 50, 60, 70]
df
nro | sukup | ikä | perhe | koulutus | palveluv | palkka | johto | työtov | työymp | palkkat | työteht | työterv | lomaosa | kuntosa | hieroja | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 38 | 1 | 1.0 | 22.0 | 3587 | 3 | 3.0 | 3 | 3 | 3 | NaN | NaN | NaN | NaN |
1 | 2 | 1 | 29 | 2 | 2.0 | 10.0 | 2963 | 1 | 5.0 | 2 | 1 | 3 | NaN | NaN | NaN | NaN |
2 | 3 | 1 | 30 | 1 | 1.0 | 7.0 | 1989 | 3 | 4.0 | 1 | 1 | 3 | 1.0 | NaN | NaN | NaN |
3 | 4 | 1 | 36 | 2 | 1.0 | 14.0 | 2144 | 3 | 3.0 | 3 | 3 | 3 | 1.0 | NaN | NaN | NaN |
4 | 5 | 1 | 24 | 1 | 2.0 | 4.0 | 2183 | 2 | 3.0 | 2 | 1 | 2 | 1.0 | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
77 | 78 | 1 | 22 | 1 | 3.0 | 0.0 | 1598 | 4 | 4.0 | 4 | 3 | 4 | NaN | 1.0 | 1.0 | NaN |
78 | 79 | 1 | 33 | 1 | 1.0 | 2.0 | 1638 | 1 | 3.0 | 2 | 1 | 2 | 1.0 | NaN | NaN | NaN |
79 | 80 | 1 | 27 | 1 | 2.0 | 7.0 | 2612 | 3 | 4.0 | 3 | 3 | 3 | 1.0 | NaN | 1.0 | NaN |
80 | 81 | 1 | 35 | 2 | 2.0 | 16.0 | 2808 | 3 | 4.0 | 3 | 3 | 3 | NaN | NaN | NaN | NaN |
81 | 82 | 2 | 35 | 2 | 3.0 | 15.0 | 2183 | 3 | 4.0 | 4 | 3 | 4 | 1.0 | NaN | NaN | NaN |
82 rows × 16 columns
# Ristiintaulukointi lukumäärinä
df1 = pd.crosstab(df['johto'], df['sukup'])
df1.index = tyytyväisyys
df1.index.name = 'tyytyväisyys johtoon'
df1.columns = sukup
df1
mies | nainen | |
---|---|---|
tyytyväisyys johtoon | ||
erittäin tyytymätön | 7 | 0 |
tyytymätön | 15 | 1 |
siltä väliltä | 23 | 7 |
tyytyväinen | 15 | 8 |
erittäin tyytyväinen | 3 | 3 |
# khiin neliö -testi lasketaan aina lukumäärätaulukosta!
p = chi2_contingency(df1)[1]
print(f'p-arvo {p:.3f}')
p-arvo 0.065
Khiin neliö -testin p-arvo on 0.065, joten riippuvuus ei ole merkitsevää.
# Ristiintaulukointi pylväinä
df1.plot(kind='bar', width=0.8, rot=45)
plt.ylabel('lukumäärä')
plt.grid(axis='x')
# Ristiintaulukointi prosentteina (mieti tarkkaan, haluatko prosentit sarakkeiden summista vai rivien summista!)
df2 = pd.crosstab(df['johto'], df['sukup'], normalize='columns')*100
df2.index = tyytyväisyys
df2.index.name = 'tyytyväisyys johtoon'
df2.columns = sukup
# Lukumäärätaulukosta (df1) n-arvot sarakeotsikoihin
for sarake in df2.columns:
df2 = df2.rename(columns={sarake:f'{sarake}, n = {df1[sarake].sum()}'})
# Ulkoasun viimeistely
df2.style.format('{:.1f} %').background_gradient(cmap='Reds')
mies, n = 63 | nainen, n = 19 | |
---|---|---|
tyytyväisyys johtoon | ||
erittäin tyytymätön | 11.1 % | 0.0 % |
tyytymätön | 23.8 % | 5.3 % |
siltä väliltä | 36.5 % | 36.8 % |
tyytyväinen | 23.8 % | 42.1 % |
erittäin tyytyväinen | 4.8 % | 15.8 % |
# Edellinen 100 % pinottuina vaakapylväinä
# T vaihtaa taulukon rivit ja sarakkeet (kaavion arvosarjat ja kategoriat) päittäin
# alpha-parametrilla hieman läpinäkyvyyttä väreihin
df2.T.plot(kind='barh', stacked=True, width=0.8, cmap='Reds', alpha=0.8, figsize=(6, 3))
plt.xlabel('Prosenttia sukupuolesta')
plt.grid(axis='y')
plt.gca().xaxis.set_major_formatter(ticks) # x-akselin prosenttimuotoilu
plt.legend(loc=(1, 0.5)) # Selitteen sijoittelu
<matplotlib.legend.Legend at 0x29256f07990>
Usean kaavion kokonaisuuksia saan seabornin catplot toiminnolla (countplot). Usean kaavion kokonaisuuksien hienosäätö vaatii jonkin verran perehtymistä.
kind-parametrin arvo count tarkoittaa countplot-kaaviolajia.
catplotin kuten muidenkin yhdistelmäkaavioiden koon määrittämiseen käytetään height- ja aspect-parametreja (kokeile eri arvoja).
# Koulutuksen jakauma sukupuolen ja perhesuhteen mukaan
g = sns.catplot(data=df, x='koulutus', col='perhe', row='sukup', kind='count',
height=3, aspect=1.5)
g.set_xticklabels(koulutus)
g.set_ylabels('lukumäärä')
# Seuraavassa määritän kaavioiden otsikot
titles = ['mies, perheetön','mies, perheellinen', 'nainen, perheetön', 'mies, perheellinen']
for ax, title in zip(g.axes.flatten(), titles):
ax.set_title(title)
Ennen ristiintaulukointia määrällinen muuttuja täytyy luokitella.
# Iän luokittelu
df['ikäluokka'] = pd.cut(df['ikä'], bins=bins, right=False)
df3 = pd.crosstab(df['ikäluokka'], df['sukup'])
df3.columns = sukup
df3
mies | nainen | |
---|---|---|
ikäluokka | ||
[20, 30) | 16 | 1 |
[30, 40) | 23 | 7 |
[40, 50) | 16 | 7 |
[50, 60) | 7 | 4 |
[60, 70) | 1 | 0 |
Usean kaavion kokonaisuuksia saan seabornin displot toiminnoilla (histplot). Usean kaavion kokonaisuuksien hienosäätö vaatii jonkin verran perehtymistä.
Kind-parametrin arvo hist tarkoittaa histplot-kaaviolajia.
# Ikäjakauma sukupuolen mukaan
g = sns.displot(data=df, x='ikä', col='sukup', kind='hist', bins=bins, height=3, aspect=1)
g.set_ylabels('lukumäärä')
# Kaavioiden otsikot sukup-listasta
for ax, title in zip(g.axes.flatten(), sukup):
ax.set_title(title)
# Palkan tunnusluvut koulutuksen mukaan
df4 = df.groupby('koulutus')['palkka'].describe()
df4.index = koulutus
df4.style.format('{:.0f}')
count | mean | std | min | 25% | 50% | 75% | max | |
---|---|---|---|---|---|---|---|---|
peruskoulu | 27 | 2310 | 473 | 1638 | 2008 | 2144 | 2534 | 3587 |
2. aste | 30 | 2403 | 534 | 1521 | 2008 | 2378 | 2729 | 3510 |
korkeakoulu | 22 | 2887 | 1108 | 1559 | 2222 | 2710 | 2925 | 6278 |
ylempi korkeakoulu | 2 | 5147 | 110 | 5069 | 5108 | 5147 | 5186 | 5225 |
# Palkkakeskiarvot koulutuksen mukaan; näkyvillä myös virhemarginaalit (musta viiva)
sns.barplot(data=df, x='koulutus', y='palkka')
# Pientä kikkailua n-arvojen saamiseksi kaavioon
n_koulutus = []
for i in range(4):
# \n on rivinvaihdon merkki
n_koulutus.append(f'{koulutus[i]}\n n = {int(df4.iloc[i, 0])}')
plt.xticks(ticks=[0, 1, 2, 3], labels=n_koulutus)
plt.ylabel('palkkakeskiarvo')
Text(0, 0.5, 'palkkakeskiarvo')
# Palkkakeskiarvot koulutuksen ja sukupuolen mukaan
ax = sns.barplot(data=df, x='koulutus', y='palkka', hue='sukup')
plt.xticks(ticks=[0, 1, 2, 3], labels=koulutus)
plt.ylabel('Palkkakeskiarvo')
# Selitteeseen tekstimuotoiset nimet
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, sukup)
<matplotlib.legend.Legend at 0x29257c09650>
# Palkka koulutuksen mukaan boxplottina
sns.boxplot(data=df, x='koulutus', y='palkka')
# n_koulutus-lista määritelty edellä palkkakeskiarvoja kuvaavan barplotin yhteydessä
plt.xticks(ticks=[0, 1, 2, 3], labels=n_koulutus)
([<matplotlib.axis.XTick at 0x29258257e90>, <matplotlib.axis.XTick at 0x292583f3810>, <matplotlib.axis.XTick at 0x29257b8d750>, <matplotlib.axis.XTick at 0x29258435990>], [Text(0, 0, 'peruskoulu\n n = 27'), Text(1, 0, '2. aste\n n = 30'), Text(2, 0, 'korkeakoulu\n n = 22'), Text(3, 0, 'ylempi korkeakoulu\n n = 2')])
# Palkka koulutuksen ja sukupuolen mukaan
ax = sns.boxplot(data=df, x='koulutus', y='palkka', hue='sukup')
plt.xticks(ticks=[0, 1, 2, 3], labels=koulutus)
handles, _ = ax.get_legend_handles_labels()
ax.legend(handles, sukup)
<matplotlib.legend.Legend at 0x292584d08d0>
Usean kaavion kokonaisuuksia saan seabornin catplot-toiminnolla (barplot, boxplot).
# Tyytyväisyys johtoon sukupuolen mukaan
g = sns.catplot(data=df, x='johto', y='palkka', col='sukup', kind='bar', height=3, aspect=1)
for ax, title in zip(g.axes.flatten(), sukup):
ax.set_title(title)
# Palkka sukupuolen ja koulutuksen mukaan
g = sns.catplot(data=df, x='sukup', y='palkka', col='koulutus', kind='box', height=3, aspect=0.8)
g.set_xlabels('')
g.set_xticklabels(sukup)
for ax, title in zip(g.axes.flatten(), koulutus):
ax.set_title(title)
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.html
# Riippumattomien otosten t-testi
# Vertailtavien ryhmien muodostaminen
s1 = df['palkka'][df['sukup']==1] # miehet
s2 = df['palkka'][df['sukup']==2] # naiset
# Kahden riippumattoman (ind) otoksen t-testi
p = ttest_ind(s1, s2, equal_var=False, nan_policy='omit')[1]
print(f'p-arvo {p:.3f}')
p-arvo 0.003
Kahden riippumattoman otoksen t-testin p-arvo on 0.003, joten miesten ja naisten palkkakeskiarvo poikkeavat merkitsevästi toisistaan.
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.f_oneway.html
# F-testi useamman ryhmän vertailuun
# Data, josta on poistettu mahdolliset palkan puuttuvia arvoja sisältävät rivit
df_dropna = df.dropna(subset=['palkka'])
# Vertailtavien ryhmien muodostaminen
k1 = df_dropna['palkka'][df_dropna['koulutus']==1] # peruskoulu
k2 = df_dropna['palkka'][df_dropna['koulutus']==2] # 2. aste
k3 = df_dropna['palkka'][df_dropna['koulutus']==3] # korkeakoulu
k4 = df_dropna['palkka'][df_dropna['koulutus']==4] # ylempi korkeakoulu
# F-testi
p = f_oneway(k1, k2, k3, k4)[1]
print(f'p-arvo {p:.3f}')
p-arvo 0.000
F-testin p-arvo on pienempi kuin 0.001, joten koulutusten välillä esiintyy merkiseviä eroja palkkakeskiarvojen välillä.
Määrällisten muuttujien välisiä riippuvuuksia tarkastelen korrelaatioiden ja hajontakaavioiden avulla.
# Avaan toisen datan, jossa asteikolla 0-10 mitattuja mielikuvia
hatco = pd.read_excel('https://taanila.fi/hatco.xlsx')
hatco
id | Delivery Speed | Price Level | Price Flexibility | Manufacturer Image | Service | Salesforce Image | Product Quality | Satisfaction Level | Firm size | Usage level % | Industry type | Buying situation | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 4.1 | 0.6 | 6.9 | 4.7 | 2.4 | 2.3 | 5.2 | 4.2 | 0 | 32 | 1 | 1 |
1 | 2 | 1.8 | 3.0 | 6.3 | 6.6 | 2.5 | 4.0 | 8.4 | 4.3 | 1 | 43 | 0 | 1 |
2 | 3 | 3.4 | 5.2 | 5.7 | 6.0 | 4.3 | 2.7 | 8.2 | 5.2 | 1 | 48 | 1 | 2 |
3 | 4 | 2.7 | 1.0 | 7.1 | 5.9 | 1.8 | 2.3 | 7.8 | 3.9 | 1 | 32 | 1 | 1 |
4 | 5 | 6.0 | 0.9 | 9.6 | 7.8 | 3.4 | 4.6 | 4.5 | 6.8 | 0 | 58 | 1 | 3 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
95 | 96 | 0.6 | 1.6 | 6.4 | 5.0 | 0.7 | 2.1 | 8.4 | 3.4 | 1 | 25 | 1 | 1 |
96 | 97 | 6.1 | 0.5 | 9.2 | 4.8 | 3.3 | 2.8 | 7.1 | 5.2 | 0 | 60 | 1 | 3 |
97 | 98 | 2.0 | 2.8 | 5.2 | 5.0 | 2.4 | 2.7 | 8.4 | 3.7 | 1 | 38 | 0 | 1 |
98 | 99 | 3.1 | 2.2 | 6.7 | 6.8 | 2.6 | 2.9 | 8.4 | 4.3 | 1 | 42 | 0 | 1 |
99 | 100 | 2.5 | 1.8 | 9.0 | 5.0 | 2.2 | 3.0 | 6.0 | 4.4 | 0 | 33 | 0 | 1 |
100 rows × 13 columns
# Muodostan datan, jossa ainoastaan mielikuvamuuttujat
hatco1 = hatco.loc[:, 'Delivery Speed':'Satisfaction Level']
# Mielikuvamuuttujien korrelaatiokertoimet
korrelaatiot = hatco1.corr()
korrelaatiot
Delivery Speed | Price Level | Price Flexibility | Manufacturer Image | Service | Salesforce Image | Product Quality | Satisfaction Level | |
---|---|---|---|---|---|---|---|---|
Delivery Speed | 1.000000 | -0.349225 | 0.509295 | 0.050414 | 0.611901 | 0.077115 | -0.482631 | 0.650632 |
Price Level | -0.349225 | 1.000000 | -0.487213 | 0.272187 | 0.512981 | 0.186243 | 0.469746 | 0.028395 |
Price Flexibility | 0.509295 | -0.487213 | 1.000000 | -0.116104 | 0.066617 | -0.034316 | -0.448112 | 0.524814 |
Manufacturer Image | 0.050414 | 0.272187 | -0.116104 | 1.000000 | 0.298677 | 0.788225 | 0.199981 | 0.475934 |
Service | 0.611901 | 0.512981 | 0.066617 | 0.298677 | 1.000000 | 0.240808 | -0.055161 | 0.631233 |
Salesforce Image | 0.077115 | 0.186243 | -0.034316 | 0.788225 | 0.240808 | 1.000000 | 0.177294 | 0.340909 |
Product Quality | -0.482631 | 0.469746 | -0.448112 | 0.199981 | -0.055161 | 0.177294 | 1.000000 | -0.283340 |
Satisfaction Level | 0.650632 | 0.028395 | 0.524814 | 0.475934 | 0.631233 | 0.340909 | -0.283340 | 1.000000 |
# Seaborn-kirjaston heatmap värittää korrelaatiokertoimia
# annot = True näyttää värien lisäksi myös lukuarvot
sns.heatmap(korrelaatiot, annot=True)
<Axes: >
# Mielikuvamuuttujien hajontakaaviot (tämän tulostuminen kestää hetken)
sns.pairplot(hatco1, kind='reg')
<seaborn.axisgrid.PairGrid at 0x29256edd250>
# Yksittäinen hajontakaavio (pienet firmat sinisellä, isot oranssilla)
plt.figure(figsize=(4, 4))
sns.scatterplot(data=hatco, x='Delivery Speed', y='Satisfaction Level', hue='Firm size')
<Axes: xlabel='Delivery Speed', ylabel='Satisfaction Level'>
# jointplot sisältää myös "reunajakaumat"
sns.jointplot(data=hatco, x='Delivery Speed', y='Satisfaction Level', hue='Firm size', height=4)
<seaborn.axisgrid.JointGrid at 0x2925bd58650>
Usean hajontakaavion kuvion voin rakentaa relplot-toiminnolla.
sns.relplot(data=hatco, x='Delivery Speed', y='Satisfaction Level', col='Buying situation', hue='Firm size',
kind='scatter', height=3, aspect=1)
<seaborn.axisgrid.FacetGrid at 0x2925bd42810>
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html
# Mielikuvamuuttujien korrelaatiokertoimet ja p-arvot muuttujan 'Satisfaction Level' kanssa
for muuttuja in hatco1:
hatco1_dropna = hatco1.dropna(subset=[muuttuja, 'Satisfaction Level'])
r, p = pearsonr(hatco1_dropna['Satisfaction Level'], hatco1_dropna[muuttuja])
print(f'{muuttuja:<18} r = {r:>6.3f}, p = {p:.3f}')
Delivery Speed r = 0.651, p = 0.000 Price Level r = 0.028, p = 0.779 Price Flexibility r = 0.525, p = 0.000 Manufacturer Image r = 0.476, p = 0.000 Service r = 0.631, p = 0.000 Salesforce Image r = 0.341, p = 0.001 Product Quality r = -0.283, p = 0.004 Satisfaction Level r = 1.000, p = 0.000
Korrelaatiot Satisfaction Level -muuttujan kanssa ovat merkitseviä paitsi muuttujan Price Level kohdalla.
Data-analytiikka Pythonilla https://tilastoapu.wordpress.com/python/