Github – esimerkit omalle koneelle

Olen tallentanut esimerkkikoodit Jypyter-notebook -muotoisina githubiin. Esimerkit aukeavat selaimeen, josta voit halutessasi kopioida koodin pätkiä (solu kerrallaan) itsellesi. Kokonaisen Jupyter-notebookin tallentaminen omalle koneelle ei välttämättä onnistu (riippuu selaimesta ja monesta muustakin asiasta). Voit kuitenkin kloonata kaikki esimerkkini kerralla omalle koneellesi seuraavasti:

  • Siirry osoitteeseen https://github.com/taanila/tilastoapu
  • Napsauta vihreää Clone or download -painiketta ja valitse Download ZIP
  • Tallenna omaan kotihakemistoosi (sinne, missä säilytät Python-koodejasi)
  • Pura (Extract all) tallentamasi paketti (tilastoapu-master)

Kaikki esimerkit ovat tämän jälkeen avattavissa ja käytettävissä Jupyterissä.

Päivitän esimerkkejä aika ajoin. Tuoreimmat versiot saat käyttöösi suorittamalla yllä kuvatun kloonauksen uudelleen.

Mainokset

Lineaarinen regressio 3

Jos koneoppiminen ja sklearn (scikit-learn) -kirjasto ovat sinulle täysin uusia, niin lue ennen tätä artikkelia Lineaarinen regressio 1 ja Lineaarinen regressio 2.

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/linreg3.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Tämän artikkelin esimerkkidatana käytän sklearn kirjastosta löytyvää dataa Bostonin asuntojen hinnoista. Artikkelin ideat olen lainannut osoitteesta

https://towardsdatascience.com/linear-regression-on-boston-housing-dataset-f409b7e4a155

Ohjelmakirjastojen tuonti

Kuvailevasta analyysistä tutut peruskirjastot ovat tarpeen:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

Datan valmistelu

Lataan esimerkkiaineiston sklearn-kirjastosta seuraavasti:

from sklearn.datasets import load_boston
boston_data = load_boston()

Aineistossa on eroteltu data (features), target (selitettävä muuttuja), feature_names (selittävien muuttujien nimet) ja DESCR (aineiston kuvaus). Tämä selviää keys()-funktiolla:

print(boston_data.keys())

dict_keys([’data’, ’target’, ’feature_names’, ’DESCR’])

Komennolla print(boston_data.DESCR) voit lukea aineiston kuvauksen.

Luon aineiston perusteella boston-nimisen dataframen:

boston = pd.DataFrame(boston_data.data, columns=boston_data.feature_names)
boston['MEDV'] = boston_data.target
boston.head()

linreg9

Selitettävä muuttuja MEDV kuvaa asuntojen mediaanihintoja tuhansina dollareina eri alueilla. Muut muuttujat ovat ehdokkaita mediaanihintaa selittäviksi muuttujiksi (voit lukea lisää aineiston kuvauksesta).

Varmuuden vuoksi kannattaa tarkistaa, onko datassa puuttuvia arvoja:

boston.isnull().sum()

linreg10

Puuttuvia arvoja ei ole, mikä on hyvä asia.

Seuraavaksi katson miten selitettävän muuttujan MEDV arvot ovat jakautuneet. Käytän tarkasteluun seaborn-kirjaston distplot-kuviota ja luokittelen hinnat 30 luokkaan:

sns.distplot(boston['MEDV'], bins=30)

linreg11

Kuvion mukaan asuntojen mediaanihintojen jakauma on oikealle vino. Vinoudesta huolimatta yritetään mallintaa hintoja lineaarisella regressiolla.

Selittävien muuttujien valinnassa voin hyödyntää korrelaatiokertoimia. Seaborn-kirjaston heatmap-funktiolla voin värjätä corr()-funktiolla lasketut korrelaatiokertoimet niiden arvon mukaan. Oletusarvolla heatmap tulostaa vain värilliset ruudut, mutta lisäparametrilla annot=True saan myös korrelaatiokertoimien arvot näkyviin:

correlation_matrix = boston.corr().round(2)
plt.figure(figsize=(12,9))
sns.heatmap(data=correlation_matrix, annot=True)

linreg12

Kaikkein eniten MEDV-muuttujan kanssa korreloivat RM (0,7) ja LSTAT (-0,74). Katson vielä kuvion avulla miltä kyseisten muuttujien korrelaatio MEDV-muuttujan kanssa näyttää. Seuraava koodi toimii vaikka lisäisit enemmänkin muuttujia features-listaan:

features = ['LSTAT', 'RM']
target = boston['MEDV']
plt.figure(figsize=(10, 5))
for i, col in enumerate(features):
   plt.subplot(1, len(features) , i+1)
   plt.scatter(boston[col], target)
   plt.xlabel(col)
   plt.ylabel('MEDV')

linreg13

Riippuvuus on selkeää molemmissa tapauksissa, mutta erityisesti LSTAT-muuttujan kohdalla riippuvuus ei ole täysin suoraviivaista. Tästä huolimatta jatketaan eteenpäin.

Valmistellaan vielä X (features-matriisi) ja y (target) mallintamista varten:

X = boston[['LSTAT', 'RM']]
y = boston['MEDV']

Opetusdata ja testidata

Jos dataa on riittävästi, niin kannattaa jakaa se opetusdataan ja testidataan. Testidatan avulla voidaan arvioida opetusdatan perusteella laadittua mallia. Seuraavassa jaan datan sattumanvaraisesti opetusdataan ja testidataan (20 % datasta). Parametri random_state asettaa satunnaislukugeneraattorin siemenluvun. Jos jätän sen asettamatta, niin saan eri kerroilla erilaisen jaon opetusdataan ja testidataan. Käyttämällä samaa siemenarvoa saan aina saman jaon.

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, 
   test_size = 0.2, random_state=5)
print(X_train.shape)
print(X_test.shape)

(404, 2)
(102, 2)

Huomaan, että opetusdatassa on 404 havaintoa ja testidatassa 102 havaintoa.

Mallin sovitus

Mallin sovitukseen kuuluu LinearRegression-mallin tuonti lineaaristen mallien kirjastosta ja mallin sovitus fit-funktiolla.

from sklearn.linear_model import LinearRegression
malli = LinearRegression()
malli.fit(X_train, y_train)

Tuloksesta näen  mallin lähtötiedot, joita olisin halutessani voinut säätää:

LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

Mallin sopivuuden arviointi

Mallin sopivuutta arvioin keskivirheen ja selityskertoimen avulla.

RMSE (root mean squared error) eli keskivirhe lasketaan seuraavasti: lasketaan toteutuneiden havaintojen ja mallin ennustamien arvojen erotusten neliöt yhteen ja jaetaan havaintojen lukumäärällä (mean squared error); lopuksi otetaan neliöjuuri.

Mean squared error löytyy sklearn.metrics-kirjastosta:

from sklearn.metrics import mean_squared_error

y_train_predict = malli.predict(X_train)
rmse = (np.sqrt(mean_squared_error(y_train, y_train_predict)))
r2 = malli.score(X_train,y_train)

print('Mallin sopivuus opetusdataan')
print("--------------------------------------")
print('Keskivirhe: {}'.format(rmse))
print('Selityskerroin: {}'.format(r2))
print("\n")

y_test_predict = malli.predict(X_test)
rmse = (np.sqrt(mean_squared_error(y_test, y_test_predict)))
r2 = malli.score(X_test, y_test)

print('Mallin sopivuus testidataan')
print('--------------------------------------')
print('Keskivirhe: {}'.format(rmse))
print('Selityskerroin: {}'.format(r2))

Mallin sopivuus opetusdataan
————————————–
Keskivirhe: 5.6371293350711955
Selityskerroin: 0.6300745149331701

Mallin sopivuus testidataan
————————————–
Keskivirhe: 5.137400784702911
Selityskerroin: 0.6628996975186953

Katson vielä kuviona, miten hyvin ennustaminen onnistuu testidatassa:

plt.scatter(y_test, y_test_predict)
plt.xlabel('y_test')
plt.ylabel('y_test_predict')

linreg14

Malli on sitä parempi, mitä lähempänä suoraa viivaa kuvion pisteet ovat.

Mallin parantaminen

Edellä esimerkkinä laskettu malli on kelvollinen, mutta voisin parantaa mallia monin tavoin:

Asuntojen mediaanihinnat eivät ole normaalisti jakautuneet (jakauma on oikealle vino). Mallin parantamiseksi voisin kokeilla jompaa kumpaa seuraavista:

  • pudotan jakauman yläpään suurimmat mediaanihinnat pois opetusdatasta
  • muunnan mediaanihintoja paremmin normaalijakaumaa vastaaviksi.

Selittävien muuttujien ja mediaanihintojen välinen riippuvuus ei ollut aivan suoraviivainen. Tämä saattaisi korjaantua, jos muunnan mediaanihinnat paremmin normaalijakaumaa vastaaviksi. Tarvittaessa voin tehdä myös selittäville muuttujille muunnoksia tai voin valita malliksi ei-lineaarisen mallin.

Malliin voin myös lisätä selittäviä muuttujia. PTRATIO-muuttujan ottamista selittäväksi muuttujaksi kannattaisi kokeilla.

Jos teen korjauksia malliin, niin voin arvioida korjatun mallin sopivuutta vertaamalla sen keskivirhettä ja selityskerrointa tämän artikkelin malliin.

Lineaarinen regressio 2

Jos koneoppiminen ja sklearn (scikit-learn) -kirjasto ovat sinulle täysin uusia, niin lue ennen tätä artikkelia Lineaarinen regressio 1

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/linreg2.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Tämän artikkelin esimerkeissä käytän datoja http://taanila.fi/mokki.xlsx ja http://taanila.fi/mokkinew.xlsx

Lineaarisella regressiomallilla voidaan ennustaa jatkuvaluonteisen muuttujan arvoja selittävien muuttujien avulla, jos selittävien muuttujien ja ennustettavan muuttujan välillä on likimain lineaarinen (suoraviivainen) riippuvuus.

Lineaarista regressiomallia voidaan pitää koneoppimisen mallina, jos kone oppii mallin parametrit olemassa olevan datan perusteella.

Tarkastelen esimerkkinä kuvitteellista aineistoa kesämökkien hinnoista. Hintaa selittävinä muuttujina ovat rantaviivan pituus metreinä, mökin pinta-ala neliömetreinä ja dikitominen muuttuja sähköliittymästä (1 = sähköliittymä, 0 = ei sähköliittymää).

Ohjelmakirjastojen tuonti

Tuon kuvailevasta analyysista tutut kirjastot (numpy-kirjastoa en tällä kertaa tarvitse):

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

Datan valmistelu

Luen datan Excel-tiedostosta dataframeen:

df=pd.read_excel('http://taanila.fi/mokki.xlsx')
df

linreg4

Muodostan feature-matriisin selittävistä muuttujista ranta, pinta-ala ja sähkö. Selitettäväksi muuttujaksi (target) tulee hinta.

X=df[['ranta', 'pinta-ala','sähkö']]
y=df['hinta']

Mallin sovitus

Tuon lineaaristen mallien kirjastosta LinearRegression-mallin. Mukavuussyistä annan mallille nimeksi malli.

Sovitan mallin dataan fit-funktiolla.

from sklearn.linear_model import LinearRegression
malli=LinearRegression()
malli.fit(X,y)

Tuloksesta näen  mallin lähtötiedot, joita olisin halutessani voinut säätää:

LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

Mallin vakiotermi:

malli.intercept_

-96.94145434036429

Selittävien muuttujien kertoimet:

malli.coef_

array([ 1.9750098 ,  2.77578415, 20.29877373])

Mallin sopivuuden arviointi

Mallin selityskerroin:

malli.score(X,y)

0.9819136190845801

Selityskertoimen mukaan 98,2 % hinnan varianssista voidaan selittää selittävien muuttujien avulla.

Mallin sopivuutta voin arvioida myös virhetermejä (ennusteen ero toteutuneeseen hintaan) tarkastelemalla:

plt.scatter(malli.predict(X), malli.predict(X)-y)
plt.hlines(y=0,xmin=50,xmax=250)
plt.xlabel('Ennuste')
plt.ylabel('Poikkeama todellisesta')

linreg5

Virhetermit ovat melko satunnaisesti jakautuneet, mikä on hyvä asia.

Seuraavassa tarkastelen vielä pistekuviona toteutunutta hintaa ja mallin ennustamaa hintaa:

plt.scatter(df['hinta'], malli.predict(X))
plt.xlabel('Todellinen hinta')
plt.ylabel('Ennuste')

linreg6

Ennustaminen

Mallin perusteella voin laskea hintaennusteita uudelle datalle, jota ei käytetty mallin laatimiseen:

Xuudet=pd.read_excel('http://taanila.fi/mokkinew.xlsx')
Xuudet['Hintaennuste']=malli.predict(Xuudet)
Xuudet

linreg7

Perinteisempi regressiotuloste

Halutessani saan perinteisemmän regressiotulosteen statsmodels-kirjaston toiminnoilla:

import statsmodels.api as sm
X = sm.add_constant(X)
malli_sm = sm.OLS(y, X)
results = malli_sm.fit()
print(results.summary())

linreg8

Lineaarinen regressio 1

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/linreg1.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Tämän artikkelin esimerkeissä käytän dataa http://taanila.fi/linreg1.xlsx

Yleistä koneoppimisen malleista

Tästä artikkelista opit sklearn (scikit-learn) -ohjelmakirjaston koneoppimisen mallien käyttöliittymän pelkistetyn esimerkin avulla. Käyttöliittymä sklearn-kirjaston malleihin on yksinkertainen sisältäen seuraavat vaiheet:

Valmistele data

Ohjatun (supervised) oppimisen malleissa datasta täytyy erottaa selittävät muuttujat (feature-matriisi , x-muuttujat) ja selitettävä muuttuja (label, target, y-muuttuja).

Ohjaamattomissa (unsupervised) malleissa tarvitaan ainoastaan feature-matriisi.

Tuo malli ja säädä lähtötiedot

Esimerkiksi tässä artikkelissa käytettävä lineaarinen regressiomalli tuodaan seuraavasti:

from sklearn.linear_model import LinearRegression

Lineaaristen mallien kirjastosta siis tuodaan LinearRegression-malli. Mallin lähtötietojen säätäminen edellyttää käytettävän mallin tuntemusta. Jos et ole perehtynyt malliin, niin voit tyytyä lähtötietojen oletusarvoihin.

Sovita malli (fit)

Tässä vaiheessa suoritetaan varsinainen laskenta. Onneksi valmiit algoritmit hoitavat laskennan.

Arvioi mallin sopivuutta

Sopivuuden arviointiin on monia menetelmiä, esimerkiksi selityskertoimen laskeminen, visuaalinen tarkastelu tai mallin toimivuuden testaaminen testidatalla.

Ennusta (predict)

Ennakoivassa analytiikassa keskeisin vaihe on tietysti ennusteiden laskeminen uudelle datalle.

Lineaarinen regressio

Lineaarisella regressiomallilla voidaan ennustaa jatkuvaluonteisen muuttujan arvoja selittävien muuttujien avulla, jos selittävien muuttujien ja ennustettavan muuttujan välillä on likimain lineaarinen (suoraviivainen) riippuvuus.

Lineaarista regressiomallia voidaan pitää koneoppimisen mallina, jos kone määrittää (oppii) mallin parametrit olemassa olevan datan (training data) perusteella.

Ohjelmakirjastojen tuonti

Kuvailevasta analyysistä tutut peruskirjastot ovat tarpeen:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

Datan valmistelu

Tässä esimerkissä luen datan Excel-tiedostosta dataframeen:

df = pd.read_excel('linreg1.xlsx')
df

linreg1

Yritän selittää myyntiä mainoskuluilla, joten määritän mainoskulut selittäväksi muuttujaksi (x) ja myynnin selitettäväksi muuttujaksi (y). Feature-matriisin täytyy olla mallia sovitettaessa dataframe-muodossa, joten teen muunnoksen to_frame-funktiolla.

x=df['Mainoskulut 1000 €']
X=df['Mainoskulut 1000 €'].to_frame() #feature-matriisi
y=df['Myynti 1000 €'] #target
plt.scatter(x,y)

linreg2

Pistekaavion perusteella riippuvuus mainoskulujen ja myynnin välillä näyttää likimain lineaariselta (suoraviivaiselta).

Mallin sovitus

Tuon LinearRegression-mallin lineaaristen mallien kirjastosta ja nimeän sen yksinkertaisuuden vuoksi nimellä malli.

from sklearn.linear_model import LinearRegression
malli=LinearRegression()
malli.fit(X,y)

Tulosteena saan mallin lähtötiedot (joita olisin voinut halutessani säätää):

LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

Mallin sovittamisen jälkeen voin katsoa mallin parametreja. Yksinkertaisen lineaarisen regression tapauksessa mallin parametrit ovat suoran kulmakerroin ja vakiotermi.

malli.coef_ #kulmakerroin

array([52.56756757])

Kulmakerroin tulee listamuodossa (array), koska mallissa voisi olla useampia selittäviä muuttujia ja näin ollen myös useampia kulmakertoimia.

malli.intercept_ #vakiotermi

46.486486486486505

Mallina on siis suora, jonka yhtälö: y = 52,568x + 46,486

Mallin sopivuuden arviointi

Mallin selityskertoimen saan:

malli.score(X,y)

0.7663982928521625

Voin siis todeta: 76,6 % myynnin varianssista voidaan selittää mainoskuluilla.

Voin tarkastella mallin sopivuutta myös graafisesti:

xfit=np.linspace(0.4,1.4) #50 arvoa tasavälein väliltä 0.4 - 1.4
Xfit=pd.DataFrame(xfit)
yfit=malli.predict(Xfit)
plt.scatter(x,y)
plt.plot(xfit,yfit)
plt.xlabel('Mainoskulut 1000 €')
plt.ylabel('Myynti 1000 €')

linreg3

Ennustaminen

Jos mainoskuluihin suunnitellaan käytettäväksi 900 euroa, niin mallin mukainen myyntiennuste:

malli.predict(0.9)

array([93.7972973])

Myyntiennusteeksi saadaan siis 93797 euroa.

Yhteenveto

Yksinkertaisimmillaan tuodaan ja määritellään malli, sovitetaan malli ja ennustetaan:

from sklearn.linear_model import LinearRegression 

malli=LinearRegression() 

malli.fit(X,y)

malli.predict(0.9)

 

Data-analytiikkaa Pythonilla

Data-analytiikka antaa vastauksia kysymyksiin

Data-analytiikka on tavoitteellista toimintaa: tavoitteena on vastata kysymyksiin. Data-analytiikan avulla vastataan monenlaisiin kysymyksiin:

  • Minkälainen ikäjakauma asiakkaillamme on?
  • Mihin toimintamme osa-alueisiin asiakkaamme ovat tyytymättömiä?
  • Onko asiakkaan iällä yhteyttä asiakastyytyväisyyteen?
  • Miten yrityksen työilmapiiri on muuttunut viimevuodesta?
  • Ketkä asiakkaistamme ovat vaarassa siirtyä kilpailijalle?
  • Keille tuotteen markkinointikampanja kannattaa suunnata?
  • Mikä mainosvaihtoehdoista tehoaa parhaiten kohderyhmään?
  • Mitä oheistuotteita verkkokaupasta ostaneella kannattaa tarjota?
  • Mikä on tuotteen ennustettu kysyntä ensi kuussa?
  • Liittyykö vakuutuskorvaushakemukseen vakuutuspetos?
  • Millä todennäköisyydellä laina-asiakas ei pysty maksamaan lainaansa takaisin?

Data

Tavoitteen (kysymykset, joihin halutaan vastata) asettamisen jälkeen pitää selvittää minkälaista dataa tarvitaan. Data voi olla esimerkiksi:

  • Yrityksen tietokannoista löytyvää dataa (esimerkiksi CRM- ja ERP-järjestelmistä).
  • Erilaisten tiedontuottajien tarjoamaa ilmaista tai maksullista dataa.
  • Varta vasten kyselytutkimuksella tai kokeellisella tutkimuksella kerättyä dataa.
  • Erilaisten sensorien/mittalaitteiden mittaamaa dataa.

Data-analytiikan tarpeisiin datan täytyy olla taulukkomuotoista. Yleisiä data-analytiikkaan sopivia tiedostomuotoja ovat pilkkueroteltu tekstimuoto (.csv) ja Excel-muoto (.xls tai .xlsx). Tietokannoista data haetaan kyselyiden avulla. Nettikyselyohjelmista datan saa yleensä ulos pilkkuerotellussa tekstimuodossa tai Excel-muodossa.

Kun sopiva data on olemassa, niin datasta saadaan vastauksia kysymyksiin seuraavien vaiheiden kautta:

  • Datan valmistelu.
  • Datan kuvailu.
  • Tilastollinen merkitsevyys: Tämä vaihe tulee kyseeseen, jos data pohjautuu isommasta joukosta poimittuun otokseen. Tilastollinen merkitsevyys kertoo, millä varmuudella otoksessa havaittuja eroja ja riippuvuuksia voidaan yleistää isompaan joukkoon.
  • Koneoppiminen ja ennakoiva analytiikka.

Datan valmistelu

Datan valmistelu on yleensä data-analytiikan aikaa vievin vaihe. Ensimmäiseksi kannattaa varmistaa datan taulukkomuotoisuus: muuttujien nimet / kenttien nimet / sarakeotsikot ovat ensimmäisellä rivillä, datassa ei ole tarpeettomia tyhjiä rivejä tai sarakkeita, kuhunkin tilastoyksikköön/havaintoyksikköön liittyvät tiedot ovat yhdellä rivillä. Datan valmistelu voi sisältää muiden muassa seuraavia:

  • Muuttujien uudelleen nimeäminen: jatkotoimet sujuvat sutjakkaammin, jos nimet ovat lyhyitä ja helposti tunnistettavia.
  • Desimaalipilkkujen tarkistaminen: vaikka Suomessa desimaalipilkkuna käytetään pilkkua, niin Pythonissa täytyy käyttää pistettä.
  • Päivämäärien muuntaminen päivämääriksi tunnistettavaan muotoon.
  • Mittayksiköiden tarkistaminen ja tarvittavien muunnosten tekeminen.
  • Puuttuvien arvojen käsittely: poistetaanko puuttuvia arvoja sisältävät rivit, korvataanko puuttuvat arvot jollain, miten puuttuvia arvoja merkitään?
  • Uusien muuttujien laskeminen: esimerkiksi summamuuttuja useasta mielipidemuuttujasta, tilauksen hinta tilausmäärän ja yksikköhinnan avulla jne.
  • Arvojen luokittelu ja uudelleenkoodaaminen: esimerkiksi ikäluokat iän arvoista.

Datan kuvailu

Datan kuvailu voi sisältää seuraavia:

  • Lukumäärä- ja prosenttiyhteenvetojen laskeminen (frekvenssitaulukot).
  • Tilastollisten tunnuslukujen laskeminen (keskiarvo, keskihajonta, viiden luvun yhteenveto).
  • Riippuvuuksien tarkastelu (ristiintaulukoinnit, korrelaatiot).
  • Prosenttimuutosten laskeminen aikasarjoille.
  • Aikasarjojen tarkastelu viivakuvioina.
  • Liukuvien keskiarvojen esittäminen aikasarjojen yhteydessä.

Kuvailun tuloksia kannattaa visualisoida ja havainnollistaa hyvin viimeistellyillä taulukoilla ja kuvioilla.

Tilastollinen merkitsevyys

Jos käytetty data on otos isommasta perusjoukosta, niin tulokset kuvaavat otosta. Jos tarkoituksena on arvioida koko perusjoukkoa, niin otoksessa havaittujen erojen ja riippuvuuksien tilastollinen merkitsevyys kertoo, millä varmuudella eroja ja riippuvuuksia voidaan yleistää perusjoukkoon.

Koneoppiminen ja ennakoiva analytiikka

Koneoppimisen malleilla voidaan luokitella (asiakkaat luottoriski-asiakkaisiin ja muihin, vakuutuskorvaushakemukset selviin tapauksiin ja petokselta haiskahtaviin jne.) ja ennakoida (tulevaa kysyntää jne.). Koneoppiminen perustuu siihen, että kone oppii käytettävän mallin parametrit olemassa olevasta datasta ja tämän jälkeen mallia voidaan soveltaa uuteen dataan.

Koneoppimisalgoritmit voidaan luokitella  seuraavasti (suomennokset eivät ole vakiintuneita):

  • Supervised learning (ohjattu oppiminen): Algoritmi opetetaan opetusdatalla (training data). Esimerkiksi roskapostisuodatin opetetaan sähköpostidatalla, jossa on erilaisia tietoja kustakin sähköpostiviestistä sekä tieto siitä oliko sähköpostiviesti roskapostia. Tämän datan perusteella muodostuu malli, jota käyttäen tulevista sähköpostiviesteistä voidaan tunnistaa roskapostiviestit.
  • Unsupervised learning (ohjaamaton oppiminen): Esimerkiksi asiakkaiden jakaminen asiakassegmentteihin.
  • Reinforcement learning (vahvistusoppiminen): Algoritmi suorittaa toimia ja saa niistä palautetta palkkioiden ja rangaistuksen muodoissa. Algoritmi oppii saamistaan palkkioista ja rangaistuksista. Vahvistettua oppimista käytetään esimerkiksi robotiikassa.

Seuraavassa jaotellaan ohjattu ja ohjaamaton oppiminen edelleen alatyyppeihin:

kone1

Ohjattu oppiminen

Label tarkoittaa ennakoitavaa asiaa (selitettävää muuttujaa).

Diskreetti label

Jos ennakoitava asia on diskreetti (epäjatkuva), niin kyseeseen tulevat luokittelua suorittavat algoritmit, esimerkiksi logistinen regressio tai päätöspuut.

Esimerkkejä, joissa on diskreetti label:

  • Roskapostisuodatin: Label on tieto siitä, onko sähköpostiviesti roskapostia vai ei?
  • Lääketieteellinen diagnoosi: Label on tieto siitä, onko tutkitulla potilaalla tietty sairaus vai ei?
  • Vakuutuspetosten tunnistaminen: Label on tieto siitä, liittyykö korvaushakemukseen petos vai ei?

Jatkuva label

Jos ennakoitava asia on jatkuva, niin kyseeseen tulevat esimerkiksi regressiomallit ja aikasarjaennustamisen menetelmät. Esimerkkejä, joissa on jatkuva label:

  • Osakehuoneiston hinnan ennustaminen: Label on asunnon hinta.
  • Kysynnän ennustaminen aikaisemman kysynnän perusteella: Label on kysyntä.

Ohjaamaton oppiminen

Ohjaamattomassa oppimisessa ei ole labelia (selitettävää/ennakoitavaa muuttujaa). Ohjaamattoman oppimisen algoritmi muodostaa mallin suoraan datasta. Esimerkkinä asiakassegmenttien määrittäminen asiakasdatan pohjalta. Paljon käytetty algoritmi on k-means clustering.

Jos datassa on paljon muuttujia, jotka mittaavat osittain samoja asioita, niin dimensionality reduction -tyyppisillä algoritmeilla voidaan pienentää muuttujien määrää yhdistämällä niitä kimpuiksi. Tunnetuin algoritmi tähän tarkoitukseen on pääkomponenttianalyysi.

Data-analytiikkaa Pythonilla

Jos aiot käyttää Pythonia data-analytiikassa, niin kannattaa aloittaa asentamalla Anaconda.

Aikasarjat

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/aikasarjat.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Tämän artikkelin esimerkeissä käytän osoitteesta

http://www.nasdaqomxnordic.com/osakkeet/historiallisetkurssitiedot

noutamiani csv-muotoisia historiatietoja Elisan ja Telian osakkeiden kurssikehityksestä:

Ohjelmakirjastojen tuonti

Aikasarjojen käsittelyyn ja kuvamiseen tarvitsen ainakin pandas ja matplotlib.pyplot kirjastoja:

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

Aikasarjojen avaaminen dataframeen

Jos aikasarjan sisältävä tekstimuotoinen tiedosto on tallennettu työasemalleni, niin voin kurkistaa sen sisään esimerkiksi seuraavasti:

aikas1

Yllä olevan perusteella huomasin että nasdaqomxnordicin sivuilta noudetut historialliset kurssitiedot on tallennettu seuraavasti:

  • erottimena puolipiste
  • desimaalipisteenä pilkku
  • ensimmäinen rivi ei sisällä varsinaista dataa.

Edellä olevan perusteella päädyin avaamaan aikasarjan dataframeen seuraavila parametreillä:

elisa = pd.read_csv('ELISA-2016-01-01-2018-10-19.csv', sep = ';', 
   decimal = ',', skiprows = 1, usecols = 
   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
telia = pd.read_csv('TELIA1-2016-01-01-2018-10-19.csv', sep = ';', 
   decimal = ',', skiprows = 1, usecols = 
   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

Aikasarjasta tulee kunnon aikasarja sijoittamalla aikaleimat dataframen rivi-indeksiksi. Varmistan pd.to_datetime()-funktiolla, että aikaleimat tunnistetaan päivämääriksi. Lisäksi järjestän indeksin päivämäärien mukaan vanhimmasta uusimpaan:

elisa.index = pd.to_datetime(elisa['Date'])
elisa.sort_index(inplace = True)

telia.index = pd.to_datetime(telia['Date'])
telia.sort_index(inplace = True)

Viivakaavio

Viivakaavion piirtäminen esimerkiksi päätöshinnoista (Closing price) onnistuu helposti:

elisa['Closing price'].plot()

aikas2

Voin tarkastella kuviossa vain rajattua osaa aikasarjasta, esimerkiksi syyskuusta 2018 alkaen:

elisa['Closing price']['2018-09':].plot()

aikas3

Liukuva keskiarvo

Liukuvan keskiarvon voin laskea rolling()-funktiolla:

elisa['Closing price'].plot()
elisa['Closing price'].rolling(200).mean().plot()

aikas4

Halutessani voin tallentaa liukuvat keskiarvot dataframeen:

elisa['rolling200'] = elisa['Closing price'].rolling(200).mean()

Voin korvata mean()-funktion ja laskea liukuvasti myös muita tilastollisia tunnuslukuja.

Kaksi aikasarjaa samaan kaavioon

Esimerkkiaikasarjojeni arvot ovat eri suuruusluokkaa (Elisan osake kymmeniä euroja, Telian osake muutamia euroja). Jos haluan esittää tällaiset aikasarjat samassa kaaviossa ”päällekkäin”, niin tarvitsen kummallekin sarjalle oman arvoakselin.

Seuraavassa lisään ensin kaavion, jonka koon määritän oletusta suuremmaksi. Ensimmäisen sarjan väriksi valitsen käytössä olevan teeman ensimmäisen värin (C0). Toiselle sarjalle määritän oman akselin (ax2 = ax1.twinx()) ja väriksi valitsen käytössä olevan teeman toisen värin (C1).

fig, ax1 = plt.subplots(figsize = (10, 6))

color = 'C0'
#ax1.set_xlabel('Aika')
ax1.set_ylabel('Elisa', color=color)
ax1.plot(elisa['Closing price'], color=color)
ax1.tick_params(axis='y', labelcolor=color)

ax2 = ax1.twinx()
color = 'C1'
ax2.set_ylabel('Telia', color=color)
ax2.plot(telia['Closing price'], color=color)
ax2.tick_params(axis='y', labelcolor=color)

aikas5

Muutosprosentit

Muutosprosentit lasken kätevästi:

elisa['Elisa_Change'] = elisa['Closing price'].pct_change()
telia['Telia_Change'] = telia['Closing price'].pct_change()

Seuraavassa teen muutosprosenteista oman dataframen:

muutokset = pd.concat([elisa['Elisa_Change'], 
   telia['Telia_Change']], axis=1)
muutokset.head()

aikas6

Seuraavassa piirrän muutokset syyskuusta alkaen viivakaavioon:

muutokset['2018-09':].plot()

aikas7

Liukuva korrelaatio

Esimerkiksi 125 päivän liukuva korrelaatio kertoo viimeisimmän 125 päivän korrelaation kahden aikasarjan arvojen välillä. Liukuvaa korrelaatiota laskettaessa on syytä käyttää prosenttimuutoksia.

korrelaatio=muutokset['Elisa_Change'].rolling(125, 
   min_periods=30).corr(muutokset['Telia_Change'])
korrelaatio.plot()

aikas8

Yllä Elisan ja Soneran prosenttimuutokset ovat aluksi korreloineet voimakkaasti, mutta vuoden 2018 syyskuun vaiheilla korrelaatiokerroin on pudonnut lähelle arvoa 0,2.

Tarkastelu viikonpäivittäin

Jos haluan tutkia, onko tuottoprosenteissa eroa eri viikonpäivien välillä, niin tallennan ensin viikonpäivät dataframeen. Sen jälkeen voin laskea tuottoprosentin tunnusluvut eri viikonpäiville (0 = maanantai):

muutokset['Weekday'] = muutokset.index.weekday
muutokset['Elisa_Change'].groupby(muutokset['Weekday']).describe()

aikas9

Lisätietoa

Jos olet kiinnostunut aikasarjaennustamisesta, niin tutustu myös artikkeleihin

 

 

 

Olennaiset taidot kuvailevaan analyysiin

Tämän artikkelin ohjelmakoodin ja tulosteet löydät GitHubista:

https://github.com/taanila/tilastoapu/blob/master/olennaiset.ipynb

Jos kopioit koodia itsellesi, niin kannattaa käyttää GitHubia. Tästä artikkelista kopioidut koodit eivät välttämättä toimi oikein.

Oletan, että lukijalla on asennettuna Anaconda ja sen mukana tuleva Jupyter notebook.

Tämän artikkelin esimerkeissä käytän dataa http://taanila.fi/data1.xlsx

Johdanto

Kuvailevan analyysiin voin vaiheistaa esimerkiksi seuraavasti:

  • Ohjelmakirjastojen tuonti
  • Datojen avaaminen
  • Dataan tutustuminen
  • Datan valmistelu
  • Datan kuvailu (lukumäärät, prosentit, tunnusluvut, korrelaatiot)
  • Tulosten viimeistely
  • Tulosten siirtäminen raporttiin

Käyn läpi kuvailevan analyysin vaiheiden olennaisimmat taidot omissa luvuissaan. Tulosten viimeistelylle en varaa omaa lukuaan, vaan käsittelen aihetta sekä Datan kuvailu -luvussa että Tulosten siirtäminen raporttiin -luvussa.

Ohjelmakirjastojen tuonti

Olennaiset kirjastot datojen analysointiin ovat numpy, pandas ja matplotlib.pyplot. Vakiintuneen tavan mukaisesti käytän niille lyhenteitä np, pd ja plt.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

%matplotlib inline

Viimeisen rivin (%matplotlib inline) ansiosta kuviot tulostuvat Jupyter-notebookiin ilman erillistä tulostuskomentoa.

Datojen avaaminen

Datan luen dataframe-tyyppisen muuttujan arvoksi. Jos data-tiedosto on netissä, niin käytän nettiosoitetta; jos data on työasemalla, niin käytän tiedostopolkua. Jos data on samassa kansiossa ohjelmakoodin kanssa, niin pelkkä tiedostonimi riittää.

Tekstitiedosto: pd.read_csv()

Jos data on csv-muotoisena tekstiedostona, niin avaaminen sujuu pd.read_csv() -funktiolla:

df = pd.read_csv('http://taanila.fi/data1.csv', 
    sep = ';', decimal = ',')

Suomalaisilla asetuksilla tallennetuissa csv-datoissa on erottimena puolipiste ja desimaalierottimena pilkku. Jos erottimena on pilkku ja desimaalierottimena piste, niin sep– ja decimal-parametreja ei tarvitse erikseen määrittää.

Lisätietoa pd.read_csv -funktion lisäparametreista:

https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html

Excel-tiedosto: pd.read_excel()

Jos data on Excel-muodossa kuten tämän artikkelin esimerkkidata, niin avaaminen sujuu pd.read_excel() -funktiolla:

df = pd.read_excel('http://taanila.fi/data1.xlsx', 
   sheet_name = 'Data')

Vanhemmissa pandas-versioissa sheet_name sijasta täytyy käyttää sheetname. Taulukkovälilehteä (sheet_name) ei tarvitse määritellä, jos data on ensimmäisellä taulukkovälilehdellä. Jos datan yläpuolella on dataan kuulumattomia rivejä, niin tarvitaan lisäparametria skiprows.

Lisätietoa pd.read_excel -funktion lisäparametreista:

https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_excel.html

Tietokanta tai html: pd.read_sql(), pd.read_html()

Tietokantoja varten pandas sisältää pd.read_sql() -funktion ja html-sivuilta lukemista varten pd.read_html() -funktion.

Dataan tutustuminen

Datan voin tulostaa näytölle suoraan dataframen nimellä, esimerkiksi df. Pitkistä datoista vain datan alku- ja loppuosa tulostuu näkyviin.

head(), tail()

Ensimmäiset viisi riviä voin tulostaa funktiolla df.head() ja viisi viimeistä riviä funktiolla df.tail(). Jos haluan nähdä enemmän kuin 5 riviä, niin kirjoitan haluamani rivimäärän sulkeiden sisään, esimerkiksi df.head(10).

columns

Datan sarakkeiden (muuttujien) nimet näen funktiolla df.columns.

count()

Sarakkeiden ei-tyhjien arvojen lukumäärät näen funktiolla df.count().

olennaiset1

np.unique()

Sarakkeiden ainutkertaiset arvot näen numpyn unique()-funktiolla:

for var in df:
   print(var, np.unique(df[var]))

Esimerkkiaineistolleni tulosteen alku näyttää seuraavalta:

olennaiset2

Datan valmistelu

replace()

Voin koodata muuttujan arvot uudella tavalla replace()-funktiolla. Esimerkiksi seuraavassa luon uuden sukup2-muuttujan, jossa sukupuolet ovat tekstimuodossa:

df['sukup2']=df['sukup'].replace({1 : 'Mies', 2 : 'Nainen'})
df.head(6)

pd.cut()

Voin luokitella määrällisen muuttujan arvot pd.cut()-funktiolla. Seuraavassa on ensiksi määritelty luokkarajat bins-listaan.

bins = [18, 28, 38, 48, 58, 68]
df['ikä2'] = pd.cut(df['ikä'], bins = bins)
df.head()

rename()

Voin vaihtaa muuttujien nimiä:

df.rename(columns = {'sukup2': 'sukup_teksti', 
   'ikä2': 'ikäluokka'}, inplace = True)
df.head()

Muuttujien tekstimuotoiset arvot listana

Analyysit sujuvat parhaiten, jos tiedot ovat numeromuodossa. Analyysin tuloksiin haluan kategoristen ja mielipidemuuttujien tekstimuotoiset arvot. Tätä ajatellen määrittelen listat muuttujien tekstiarvoille:

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']

Datan kuvailu

Yhteenveto lukumäärinä: pd.crosstab()

Lukumäärä-yhteenvedot saan pd.crosstab()-funktiolla. Funktion toinen parametri (’n’) määrittää otsikon lukumäärille.

df1 = pd.crosstab(df['koulutus'], 'n')
df1.index = koulutus
df1.columns.name = ''
df1

Lisään koulutuksen tekstimuotoiset arvot df1-dataframen rivi-indeksiksi (df1.index = koulutus). Rivi df1.columns.name = ” (viimeisenä on kaksi hipsua) korvaa df1-dataframen vasemman yläkulman otsikon tyhjällä.

olennaiset3

Lisään lukumäärien viereen %-sarakkeen:

df1['%'] = df1/df1.sum()
df1.style.format({'%': '{:.1%}'})

Jos haluan tulostaulukot viimeistellyssä muodossa, niin tarvitsen style.format()-funktiota. Edellä määritän df1-dataframen %-sarakkeelle prosenttimuotoilun yhdellä desimaalilla.

olennaiset4

Tästä ei ole pitkä matka pylväskuvioon. df1-dataframen %-sarakkeesta saan pylväskuvion plot.barh()-funktiolla. Värimäärityksen (color = ’C0’) teen, jotta kaikki pylväät ovat saman värisiä (väri C0 tarkoittaa käytössä olevan teeman ensimmäistä väriä).

ax = df1['%'].plot.barh(color = 'C0')
vals = ax.get_xticks()
ax.set_xticklabels(['{:.0%}'.format(x) for x in vals])

olennaiset5

Arvoakselin asteikon esittämiseksi prosentteina luen ensin ax-muuttujasta asteikon arvot ja sen jälkeen muotoilen jokaisen arvon prosenteiksi ilman desimaaleja.

Monivalinta: count()

Monivalintakysymys on kysymys, jonka vaihtoehdoista vastaaja voi valita useampiakin. Monivalinnan jokainen vaihtoehto tallennataan dataan omana sarakkeenaan. Valintaa merkitään ykkösellä (tai joskus jollain muulla numerolla) ja valitsematta jättämistä tyhjällä.

Esimerkkiaineistossani työterv, lomaosa, kuntosa ja hieroja ovat olleet vaihtoehtoja, joista vastaajat ovat valinneet ne työantajan tarjoamat etuisuudet, joita ovat hyödyntäneet. Valintojen lukumäärät voin laskea count()-funktiolla:

df2=df[['työterv', 'lomaosa', 'kuntosa', 'hieroja']].count()
df2 = df2.to_frame('n').sort_values(by = 'n', ascending = False)
df2.style.format('{:.0f}')

Laskemisen tulos ei ole dataframe, vaan series. Toisella rivillä vaihdan series-tyyppisen tuloksen dataframeksi, määritän valintojen lukumääräsarakkeen nimeksi n ja järjestän dataframen valintojen mukaiseen laskevaan järjestykseen.

Muotoilen valintojen lukumäärät ilman desimaaleja esitetyiksi liukuluvuiksi (liukuluvun symboli on f; liukuluku tarkoittaa käytännössä desimaalilukua).

olennaiset6

Tämän voin esittää pylväskaaviona:

df2.sort_values(by = 'n').plot.barh(legend = False, color = 'C0')
plt.xlabel('Käyttäjien lukumäärä')

Ilman legend = False parametria kuvioon tulee tarpeeton pylväsvärin selite. Arvoakselille lisäsin otsikoksi ’Käyttäjien lukumäärä’.

olennaiset7

Jos haluan ristiintaulukoida monivalinnan valintojen lukumäärät sukupuolen kanssa, niin voin hyödyntää groupby()-funktiota:

df3 = df.groupby('sukup')['työterv', 'lomaosa', 
   'kuntosa', 'hieroja'].count()
df3.index = sukup
df3.style.format('{:.0f}')

Rivi-indeksin tekstimuotoiset arvot (mies, nainen) saan aiemmin määritellystä sukup-listasta. Lukumäärät esitän liukulukuna ilman desimaaleja.

olennaiset8

Tästä syntyy vaivatta pylväskuvio:

df3.plot.barh()
plt.xlabel('Käyttäjien lukumäärä')

olennaiset9

Ristiintaulukointi: pd.crosstab()

Edellä tarkastelin monivalinnan valintojen lukumäärän ristiintaulukointia sukupuolen kanssa. Muissa tapauksissa ristiintaulukointi sujuu pd.crosstab()-funktiolla. Ensimmäisen argumentin muuttuja sijoittuu ristiintaulukoinnin riveille ja toisen argumentin muuttuja sarakkeisin. Lisäparametrillä normalize voin määrittää laskettavaksi prosentit lukumäärien sijaan. Seuraavassa lasken prosentit sarakkeista eli sukupuolesta (columns):

df4 = pd.crosstab(df['koulutus'], df['sukup'], normalize = 'columns')
df4.index = koulutus
df4.columns = sukup
df4.style.format('{:.1%}')

Rivi-indeksin tekstimuotoiset arvot otan aiemmin määritellystä koulutus-listasta ja sarakkeen arvot sukup-listasta. Tulokset muotoilen prosenteiksi yhdellä desimaalilla.

olennaiset10

Tämän voin esittää pylväinä:

ax = df4.plot.barh()
plt.xlabel('Prosenttia sukupuolesta')
vals = ax.get_xticks()
ax.set_xticklabels(['{:.0%}'.format(x) for x in vals])

olennaiset11

Vaihtoehtoisesti voin käyttää 100% pinottuja vaakapylväitä. Tätä varten minun pitää ensin vaihtaa dataframen rivit ja sarakkeet päittäin transpose()-funktiolla:

ax = df4.transpose().plot.barh(stacked = True)
plt.xlabel('Prosenttia sukupuolesta')
vals = ax.get_xticks()
ax.set_xticklabels(['{:.0%}'.format(x) for x in vals])

olennaiset12

Useiden muuttujan yhteenvedot samaan taulukkoon: value_counts()

Esimerkkiaineistossani on muuttujina muiden muassa ’tyytyväisyys johtoon’, ’tyytyväisyys työtovereihin’, ’tyytyväisyys työympäristöön’, ’tyytyväisyys palkkaan’ ja ’tyytyväisyys työtehtäviin’. Näitä kaikkia on mitattu 5-portaisella tyytyväisyysasteikolla. Jotta voin vertailla tyytyväisyyksiä eri asioihin, haluan ne kaikki samaan taulukkoon vierekkäin. Tämän voin toteuttaa monellakin tavalla, mutta mielestäni kätevin tapa on käyttää value_counts()-funktiota:

df5 = df['johto'].value_counts(sort = False, normalize = True).to_frame()
df5['työtov'] = df['työtov'].value_counts(sort = False, normalize = True)
df5['työymp'] = df['työymp'].value_counts(sort = False, normalize = True)
df5['palkkat'] = df['palkkat'].value_counts(sort = False, normalize = True)
df5['työteht'] = df['työteht'].value_counts(sort = False, normalize = True)
df5.index = tyytyväisyys
df5.style.format('{:.1%}')

Ensimmäisellä rivillä lasken ensimmäiselle tyytyväisyys-muuttujalle vastausvaihtoehtojen 1-5 lukumäärät value_counts()-funktiolla. Lisäparametreina määritän

  • järjestämisen muuttujan arvojen mukaan (sort = False; ilman tätä järjestäminen tapahtuisi lukumäärien mukaiseen järjestykeen)
  • prosenttien esittämisen lukumäärien sijasta (normalize = True).

Lisäksi muunnan tuloksena syntyvät series-tyyppisen muuttujan dataframeksi (to_frame()).

Seuraavilla riveillä lisään dataframeen muiden tyytyväisyys-muuttujien lukumäärät.

Lopuksi lisään tekstimuotoiset rivi-indeksin arvot aiemmin määritellystä tyytyväisyys-listasta. Tulokset näytän prosenttimuodossa yhden desimaalin  tarkkuudella.

olennaiset13

Tämän voin esittää myös pinottuna pylväskuviona:

ax = df5.transpose().plot.barh(stacked = True, 
   color=['#C44E52','#D65F5F','grey','#4878CF','#4C72B0'])
vals = ax.get_xticks()
ax.set_xticklabels(['{:.0%}'.format(x) for x in vals])

Määritän itse värit oletusvärien sijaan.

olennaiset14

Luokiteltu jakauma: pd.cut()

Määrälliset muuttujat kannattaa yleensä luokitella ennen lukumäärien ja prosenttien laskemista. Esimerkkiaineistossani palkka on tällainen muuttuja.

Seuraavassa määritän luokkarajat (bins) ja luon uuden sarakkeen palkkaluokkaa varten. Varsinaisen luokittelun teen pd.cut()-funktiolla.

bins = [1000, 2000, 3000, 4000, 7000]
df['palkkaluokka'] = pd.cut(df['palkka'], bins = bins)
df6 = pd.crosstab(df['palkkaluokka'], 'n')
df6.columns.name = ''
df6

olennaiset15

Huomaa sulkumerkkien merkitys: kaarisulku tarkoittaa, että arvo ei kuulu luokkaan ja hakasulku tarkoittaa, että arvo kuuluu luokkaan. Esimerkiksi toisessa luokassa 2000 ei sisälly luokkaan, mutta 3000 sisältyy.

Palkkajakauman voin esittää histogrammina:

df['palkka'].plot.hist(bins)
plt.xlabel('Palkka')
plt.ylabel('Lukumäärä')

olennaiset16

Tunnuslukuja: describe()

Keskeisimmät tilastolliset tunnusluvut (lukumäärä, keskiarvo, keskihajonta, viiden luvun yhteenveto) lasken describe()-funktiolla. Seuraavassa lasken tunnuslukuja usealle muuttujalle ja muotoilen tunnusluvut näytettäväksi kahden desimaalin tarkkuudella:

df[['ikä', 'palveluv', 'palkka', 'johto', 'työtov', 
   'työymp', 'palkkat', 'työteht']].describe().
   style.format('{:.2f}')

olennaiset17

Pivot-taulukoita pivot_table(values = ”, index = ”, columns = ”)

Pivot-taulukoilla voin laatia monipuolisia raportteja eri tunnuslukuja käyttäen. Oletuksena pivot_table() -funktio laskee keskiarvoja. pivot_table()-funktion parametreinä ovat values (muuttuja, josta lasketaan), index (muuttujat, joiden arvot otetaan riveille) ja columns (muuttujat, joiden arvot otetaan sarakkeisiin). Esimerkiksi palkkakeskiarvot sukupuolen, perhesuhteen ja koulutuksen mukaan ryhmiteltyinä saan seuraavasti:

df7 = df.pivot_table(values = 'palkka', index = 
   ['sukup', 'perhe'], columns = 'koulutus')
df7.style.format('{:.0f}')

olennaiset18

Haluan tietysti täydentää taulukkoa sukupuolen, perhesuhteen ja koulutuksen tekstimuotoisilla arvoilla. Rivi-indeksin arvot ovat nyt kahdella tasolla ja tämän vuoksi tekstimuotoiset arvot määritellään set_levels-funktiolla:

df7.index = df7.index.set_levels(sukup, level=0)
df7.index = df7.index.set_levels(perhe, level=1)
df7.columns = koulutus
df7.style.format('{:.0f}')

olennaiset19

Voin laskea pivot-taulukkoon myös muita tunnuslukuja aggfunc-parametrin avulla. Seuraavassa lasken palkalle minimin, mediaanin, keskarvon ja maksimin eri koulutustasoille. Huomaa, että funktioina käytän numpy-kirjaston (np) funktioita.

df8 = df.pivot_table(values = 'palkka', index = 'koulutus',  
   aggfunc = [np.min, np.median, np.mean, np.max])
df8.index = koulutus
df8.columns = ['pienin', 'mediaani', 'keskiarvo', 'suurin']
df8.style.format('{:.0f}')

olennaiset20

Ruutu- ja janakaavio:  boxplot()

Ruutu- ja janakaavion (laatikko- ja viiksikaavio, boxplot, box & whisker) avulla voin visualisoida viiden luvun yhteenvedon. Seuraavassa esitän palkalle viiden luvun yhteenvedon eri koulutustasoilla:

ax = df.boxplot('palkka', by = 'koulutus')
plt.title('')
plt.suptitle('')
plt.xlabel('Koulutus')
plt.ylabel('Palkka')
ax.set_xticklabels(koulutus)

Tässä käytän pandas-kirjaston boxplot()-funktiota, joka tuottaa oletuksena kaksi otsikkoa (title, suptitle). Koodissa poistin otsikot näkyviltä. Vaaka-akselille lisäsin koulutukselle tekstimuotoiset arvot.

olennaiset21

Korrelaatio: corr()

Korrelaatiokertoimet lasken corr()-funktiolla. Seuraavassa lasken iän, palveluvuosien ja palkan väliset korrelaatiokertoimet ja esitän ne kahden desimaalin tarkkuudella:

df[['ikä', 'palveluv', 'palkka']].corr().style.format('{:.2f}')

olennaiset22

Hajontakaaviona voin esittää kahden muuttujan välisen korrelaation visuaalisesti:

df.plot.scatter('ikä', 'palkka')

olennaiset23

Tulosten siirtäminen raporttiin

Taulukot

Taulukoiden ulkoasu riippuu jonkin verran käyttämästäsi selaimesta (Jupyter notebook toimii oletusselaimessasi). Tämän artikkelin esimerkeissä olen käyttänyt Microsoftin Edge-selainta.

Julkaistavaksi tarkoitetun taulukon voin valita, kopioida ja liittää (copy – paste) esimerkiksi PowerPointiin, Wordiin tai Exceliin. Liitetty taulukko on myös taulukko ja voin muokata sitä taulukkotyökaluilla.

Python käyttää desimaalipilkkuna pistettä. PowerPointissa ja Wordissä voin helposti korvata pisteet pilkulla. Excelissä osa desimaalipisteellä varustetuista luvuista muuttuu päivämääriksi. Tämän voin estää kierrättämällä taulukko Wordin kautta ja korvaamalla pisteet pilkuilla Wordissä.

Voin myös tallentaa taulukot csv-tiedostoon. Tällöin desimaalipilkkujen kanssa ei tule ongelmia. Katso esimerkki artikkelista Frekvenssitaulukot Exceliin.

Jos taulukko kelpaa sellaisenaan julkaistavaksi, niin voin toki leikata sen ja liittää kuvana raporttiin.

Kuviot

Kuvion voin tallentaa plt.gcf().savefig -komennolla (gcf = get current figure). Tallennusformaatti määrittyy  kuviolle annettavan nimen tarkentimesta (esimerkiksi png):

plt.gcf().savefig('kuva.png')

Järjestelmäsi tukemat kuvaformaatit saat selville komennolla

plt.gcf().canvas.get_supported_filetypes()

Jos tarvitset tietyn kokoisia kuvia tai haluat käyttää tietynlaista tai tietynkokoista fonttia, niin voit tehdä määritykset heti ohjelmakirjastojen tuonnin jälkeen (ei kuitenkaan samassa Jupyter notebookin solussa kuin ohjelmakirjastojen tuonti, koska se ei jostain syystä toimi). Esimerkkejä mahdollisista määrityksistä:

plt.rc('figure', figsize = (8, 6))
plt.rc('font', family = 'sans-serif', size = 8)
plt.rc('axes', titlesize = 8 )
plt.rc('axes', labelsize = 8)
plt.rc('xtick', labelsize = 8)
plt.rc('ytick', labelsize = 8)
plt.rc('legend', fontsize = 8)

Kuvailevasta analyysistä merkitsevyystestaukseen

Jos kuvailet otosta ja haluat edetä testaamaan havaittujen erojen ja/tai riippuvuuksien tilastollista merkitsevyyttä, niin lue artikkelini

Merkitsevyyden testaus Pythonilla