Lukumäärä- ja prosentti-yhteenvedot

Tämä Jypyter-notebook sisältää likimain kaiken mitä sinun tarvitsee tietää lukumäärien ja prosenttien laskemisesta Pythonilla.

In [1]:
# Tuon tarvittavat kirjastot
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Kaavioiden automaattitulostus notebookiin ja kaavioiden tyyli
%matplotlib inline
plt.style.use('seaborn-whitegrid')

# Pylväskaavoiden prosenttiakselien tuunausta varten
from matplotlib.ticker import PercentFormatter

# ...jos prosentit desimaalilukuina
myFmt1 = PercentFormatter(xmax=1, decimals=0, symbol=' %')

# ...jos prosentit on jo valmiiksi kerrottu sadalla
myFmt100 = PercentFormatter(xmax=100, decimals=0, symbol=' %')
In [2]:
# Avaan datan
df = pd.read_excel('http://taanila.fi/data1.xlsx')

# Näytän 5 ensimmäistä riviä
df.head()
Out[2]:
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

Muuttujien tekstimuotoiset arvot

Muuttujien mahdolliset tektimuotoiset arvot kannattaa tallentaa lista-muuttujiin. Listoja on myöhemmin helppo lisätä tulostaulukoihin.

In [3]:
koulutus = ['Peruskoulu', '2. aste', 'Korkeakoulu', 'Ylempi korkeakoulu']

perhe = ['Perheetön', 'Perheellinen']

sukup = ['Mies', 'Nainen']

tyytyväisyys = ['Erittäin tyytymätön', 'Jokseenkin tyytymätön', 'Ei tyytymätön eikä tyytyväinen', 
                'Jokseenkin tyytyväinen', 'Erittäin tyytyväinen']

# Monivalintakysymyksen (mitä etuisuuksia olet käyttänyt?) vaihtoehtojen lista
monivalinnat = ['työterv', 'lomaosa', 'kuntosa', 'hieroja']

crosstab() laskee lukumäärät

crosstab() palauttaa tuloksena dataframen. Voin helposti luoda dataframeen uuden sarakkeen, johon lasken prosentit.

Kryptisen näköiseen style.format()-toimintoon kannattaa perehtyä huolella. Se ei vaikuta dataframen sisältöön, vaan ainoastaan tässä tulostetun version ulkoasuun.

plot on nopein tapa numerotiedon esittämiseen kaaviona. Tässä käytän vaakapylväitä (barh). plot palauttaa Axes-tyyppisen olion, jota voin käyttää kaavion muotoiluun. Tässä tarvitsen Axes-oliota arvoakselin lukujen muotoiluun.

In [4]:
df1 = pd.crosstab(df['koulutus'], 'n')

# Korvaan riviotsikot aiemmin määritellyllä koulutus-listalla
df1.index = koulutus

# Poistan vasemmasta yläkulmasta häiritsevän otsikon
df1.columns.name = ''

df1
Out[4]:
n
Peruskoulu 27
2. aste 30
Korkeakoulu 22
Ylempi korkeakoulu 2
In [5]:
# Lisään prosentti-sarakkeen
df1['%'] = df1/df1.sum()*100

# Muotoilen prosentit yhden desimaalin tarkkuuteen
df1.style.format({'%': '{:.1f} %'})
Out[5]:
n %
Peruskoulu 27 33.3 %
2. aste 30 37.0 %
Korkeakoulu 22 27.2 %
Ylempi korkeakoulu 2 2.5 %
In [6]:
# Vaakapylväskaavio prosenteista
ax1 = df1['%'].plot.barh()

# x-akselin otsikointi; n-arvo mukaan
n = df1['n'].sum()
ax1.set_xlabel('Prosenttia, n=' + str(n))

# x-akselin ticksien tuunaus
ax1.xaxis.set_major_formatter(myFmt100)

# Vaihtoehtoinen tapa x-akselin ticksien tuunaukseen
#ax1.set_xticklabels(['{:.0f} %'.format(x) for x in ax1.get_xticks()])

crosstab() laskee ristiintaulukoinnit

In [7]:
# Ristiintaulukointi
df2 = pd.crosstab(df['koulutus'], df['sukup'])

# Otsikot kuntoon:
df2.index = koulutus
df2.columns = sukup

df2
Out[7]:
Mies Nainen
Peruskoulu 22 5
2. aste 23 7
Korkeakoulu 15 7
Ylempi korkeakoulu 2 0
In [8]:
# Ristiintaulukointi, prosentit (normalize) sarakkeiden mukaan
df3 = pd.crosstab(df['koulutus'], df['sukup'], normalize = 'columns')

# Rivi (index) -otsikot koulutus-listasta
df3.index = koulutus

# Kikkailen n-arvot mukaan
miehet = df['koulutus'][df['sukup']==1].count()
naiset = df['koulutus'][df['sukup']==2].count()
df3.columns = ['Mies, n=' + str(miehet),'Nainen, n=' + str(naiset)]

# Loppusilaus
(df3*100).style.format('{:.1f} %')
Out[8]:
Mies, n=62 Nainen, n=19
Peruskoulu 35.5 % 26.3 %
2. aste 37.1 % 36.8 %
Korkeakoulu 24.2 % 36.8 %
Ylempi korkeakoulu 3.2 % 0.0 %
In [9]:
# Edellinen pylväinä; legend='reverse' kääntää selitteen järjestyksen
ax2 = df3.plot.barh(legend='reverse')

ax2.set_xlabel('Prosenttia sukupuolesta')

# x-akselin ticksien tuunaus
ax2.xaxis.set_major_formatter(myFmt1)
In [10]:
# Edellinen 100 % pinottuina vaakapylväinä, T vaihtaa arvosarjat ja kategoriat päittäin
ax3 = df3.T.plot.barh(stacked = True)

ax3.set_xlabel('Prosenttia sukupuolesta')

# Selitteen sijoittelu suhteessa origoon, selitteitä 4 vierekkäin
ax3.legend(loc=(-0.15, -0.25), ncol=4)
 
# x-akselin ticksien tuunaus
ax3.xaxis.set_major_formatter(myFmt1)

Useiden muuttujien frekvenssit yhteen taulukkoon

Tämän tekeminen esimerkiksi Excelillä on vaikeahkoa. Pythonissa tämä sujuu helposti value_counts()-funktion avustuksella.

In [11]:
# Lasken lukumääriä value_counts()-funktiolla ja muutan tuloksen dataframeksi
df4 = df['johto'].value_counts(sort = False, normalize = True).to_frame()

# Lisään dataframeen uusia sarakkeita
df4['työtov'] = df['työtov'].value_counts(sort = False, normalize = True)
df4['työymp'] = df['työymp'].value_counts(sort = False, normalize = True)
df4['palkkat'] = df['palkkat'].value_counts(sort = False, normalize = True)
df4['työteht'] = df['työteht'].value_counts(sort = False, normalize = True)

# Riviotsikot aiemmin määritellystä tyytyväisyys-listasta
df4.index = tyytyväisyys

df4.loc['Yhteensä'] = df4.sum()

# Loppusilaus
(df4*100).style.format('{:.1f} %')
Out[11]:
johto työtov työymp palkkat työteht
Erittäin tyytymätön 8.5 % nan % 11.0 % 40.2 % 6.1 %
Jokseenkin tyytymätön 19.5 % 3.7 % 11.0 % 23.2 % 18.3 %
Ei tyytymätön eikä tyytyväinen 36.6 % 19.8 % 36.6 % 23.2 % 35.4 %
Jokseenkin tyytyväinen 28.0 % 43.2 % 28.0 % 12.2 % 30.5 %
Erittäin tyytyväinen 7.3 % 33.3 % 13.4 % 1.2 % 9.8 %
Yhteensä 100.0 % 100.0 % 100.0 % 100.0 % 100.0 %

Seuraavassa määriteltävistä väreistä lisätietoa https://htmlcolorcodes.com/

Voit määrittää värit käyttäen värien nimiä tai hex-värikoodeja.

In [12]:
# Edellinen 100 % pylväinä
ax4 = df4.drop('Yhteensä').T.plot.barh(stacked = True, 
                    color=['#C44E52','#D65F5F','gray','#4878CF','#4C72B0'])

ax4.set_xlabel('Prosenttia vastaajista')

# Selitteen sijainti; 2 selitettä vierekkäin
ax4.legend(loc=(-0.03, 1.1), ncol=2)

# x-akselin ticksien tuunaus
ax4.xaxis.set_major_formatter(myFmt1)

Matplotlib mahdollistaa monenlaisia toteutuksia. Seuraavassa laadin viiden kaavion yhdistelmän

  • lisäparametrilla subplots=True määritän, että jokaisesta sarakkeesta oma kaavio
  • lisäparametrilla layout=(1, 5) määritän, että kaaviot sijoitetaan 1 riville, 5 rinnakkain
  • lisäparametreilla sharex=True, sharey=True määritän, että kaikilla kaavioilla on yhteiset akselit
  • lisäparametrilla figsize=(12, 2) määritän kuvion koon
  • lisäparametrilla color='maroon' määritän pylväiden väriksi maroon
  • lisäparametrilla legend=False jätän kaavioista selitteet pois
In [13]:
axes = (df4.drop('Yhteensä')*100).plot.bar(subplots=True, layout=(1, 5), sharex=True, sharey=True, 
              figsize=(12, 2), color='maroon', legend=False)

# Kuvio, jonka sisällä kaaviot sijaitsevat (gcf = get current figure)
fig = plt.gcf()
# %-merkki kuvion sijaintiin (0.08, 0.5)
fig.text(0.08, 0.5, "%")
Out[13]:
Text(0.08, 0.5, '%')

count() laskee monivalintojen yhteenvedot

Monivalintojen yhteenveto sujuu esimerkiksi count()-funktiolla. count()-funktion tulos ei ole dataframe, mutta voin muuttaa sen dataframeksi. Samalla voin järjestää monivalinnan vaihtoehdot valintojen mukaiseen järjestykseen sort_values-funktiolla.

Voin ryhmitellä tuloskset toisen muuttujan, esimerkiksi sukupuolen, mukaan groupby()-funktion avulla.

In [14]:
# Monivalintojen lukumäärät selviävät count()-funktiolla
df5=df[monivalinnat].count()

# Muutan series-tyyppisen listan dataframeksi ja järjestän lukumäärien mukaiseen järjestykseen
df5 = df5.to_frame('n').sort_values(by = 'n', ascending = False)

df5
Out[14]:
n
työterv 47
hieroja 22
lomaosa 20
kuntosa 9
In [15]:
# Prosenttia vastaajien kokonaismäärästä (shape[0])
df5['% vastaajista'] = df5['n'] / df.shape[0]*100

# Loppusilaus
df5.style.format({'% vastaajista':'{:.1f} %'})
Out[15]:
n % vastaajista
työterv 47 57.3 %
hieroja 22 26.8 %
lomaosa 20 24.4 %
kuntosa 9 11.0 %
In [16]:
# Edellinen pylväinä
ax5 = df5['n'].plot.barh(legend=False)

ax5.set_xlabel('Käyttäjien lukumäärä')
Out[16]:
Text(0.5, 0, 'Käyttäjien lukumäärä')
In [17]:
# Monivalinnat sukupuolen mukaan
df6 = df.groupby('sukup')[monivalinnat].count()

# Sukupuolet sukup-listasta, jonka määrittelin aiemmin
df6.index = sukup

df6
Out[17]:
työterv lomaosa kuntosa hieroja
Mies 35 16 8 13
Nainen 12 4 1 9
In [18]:
# Monivalinnat sukupuolen mukaan
df7 = df.groupby('sukup')[monivalinnat].count()

# Miesten ja naisten lukumäärät
miehet = df['sukup'].value_counts()[1]
naiset = df['sukup'].value_counts()[2]

# Prosentit
# iloc[0] viittaa indeksin mukaiselle 0-riville (miesten rivi)
df7.iloc[0] = df7.iloc[0] / miehet
df7.iloc[1] = df7.iloc[1] / naiset

# Kikkailen n-arvot mukaan
df7.index = ['Mies, n=' + str(miehet), 'Nainen, n=' + str(naiset)]

# Loppusilaus
(df7*100).style.format('{:.1f} %')
Out[18]:
työterv lomaosa kuntosa hieroja
Mies, n=63 55.6 % 25.4 % 12.7 % 20.6 %
Nainen, n=19 63.2 % 21.1 % 5.3 % 47.4 %
In [19]:
# Edellinen pylväinä
ax7 = df7.plot.barh(legend='reverse')

ax7.set_xlabel('% sukupuolesta')

# x-akselin ticksien tuunaus
ax7.xaxis.set_major_formatter(myFmt1)

Luokiteltu jakauma

Pandas-kirjaston cut-funktiolla voin luokitella muuttujan. Oletuksena luokan yläraja kuuluu luokkaan. Lisäparametrilla right=False voin vaihtaa luokan alarajan kuulumaan luokkaan.

In [20]:
# Määrittelen luokkarajat palkan luokittelemiseksi
bins = [1000, 2000, 3000, 4000, 7000]

# Lisään dataan palkkaluokka-sarakkeen
df['palkkaluokka'] = pd.cut(df['palkka'], bins = bins)

# Palkkaluokkiin kuuluvien lukumäärät
df8 = pd.crosstab(df['palkkaluokka'], 'n')
df8.columns.name = ''

# Prosentit
df8['%'] = df8/df8.sum()*100

# Yhteensä-rivin lisäämiseksi indeksin luokkaväliarvot on ensin muuutettava merkkijonoiksi
df8.index = df8.index.astype(str)
df8.loc['Yhteensä'] = df8.sum()

# Loppusilaus
df8.style.format({'%': '{:.1f} %'})
Out[20]:
n %
palkkaluokka
(1000, 2000] 19 23.2 %
(2000, 3000] 50 61.0 %
(3000, 4000] 8 9.8 %
(4000, 7000] 5 6.1 %
Yhteensä 82 100.0 %
In [21]:
# Edellinen kaaviona; width=1 laittaa pylväät kiinni toisiinsa
# rot=45 kääntää x-akselin ticksien nimiöitä 45 astetta
ax8 = df8.drop('Yhteensä')['n'].plot.bar(width=1, rot=45, legend=False, edgecolor='black')

ax8.set_ylabel('lukumäärä')
Out[21]:
Text(0, 0.5, 'lukumäärä')
In [22]:
# Nopein tapa luokitellun jakauman tarkasteluun on hist-kaavio
ax9 = df['palkka'].hist(bins=bins)
ax9.set_xlabel('Palkka')
ax9.set_ylabel('Lukumäärä')
Out[22]:
Text(0, 0.5, 'Lukumäärä')
In [23]:
# np.ones kikalla prosentit hist-kaavioon
# np.ones tuottaa n kpl ykkösiä, jolloin jokaiselle luokalle tulee painoksi 1/n

# Histogrammi, jossa y-akselilla prosentit
n = df['palkka'].count()
ax10 = df['palkka'].hist(bins=bins, weights=np.ones(n)/n)
ax10.set_xlabel('Palkka')
ax10.set_ylabel('Prosenttia, n='+str(n))

# x-akselin ticksien tuunaus
ax10.yaxis.set_major_formatter(myFmt1)