#!/usr/bin/env python # coding: utf-8 # In[1]: from datetime import datetime print(f'Päivitetty {datetime.now()}') # # Datan valmistelu koneoppimista varten # # Koneoppimisen malleissa käytettävän datan osalta on huomioitava: # # * Data ei saa sisältää puuttuvia arvoja. # * Kategoriset muuttujat pitää muuntaa dummy-muuttujiksi. # * Datan standardointi voi auttaa parempien mallien luomisessa. # * Paljon muista poikkeavat arvot kannattaa joissain tapauksissa poistaa. # * Sopivat muuttujien muunnokset saattavat tuottaa parempia malleja. # * Jos ennustettava muuttuja on kategorinen, jonka jokin luokka on opetusdatassa aliedustettuna, niin datan tasapainottamisen jälkeen saadaan yleensä parempi malli. # # Tämä muistio sisältää esimerkkejä yllä mainittujen seikkojen hoitamiseen. # In[2]: import numpy as np import pandas as pd # In[3]: # Avaan esimerkeissä käytettävän datan df = pd.read_excel('https://taanila.fi/data1.xlsx') # Kaikki rivit ja sarakkeet näytetään pd.options.display.max_rows = None pd.options.display.max_columns = None # Datan muuttujat listana df.columns # In[4]: # Tietoa muuttujista df.info() # ## Puuttuvat arvot # # Puuttuvat arvot voin hoitaa kahdella tavalla: # * poistamalla puuttuvia arvoja sisältävät rivit tai # * täydentämällä puuttuvat arvot tilanteeseen sopivilla arvoilla. # # Puuttuvia arvoja sisältävät rivit voin poistaa dropna()-toiminnolla: # # https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.dropna.html # # Puuttuvia arvoja voin täydentää fillna()-toiminnolla: # # https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.fillna.html # # Katson ensin dataa värjäämällä puuttuvat arvot: # In[5]: df.style.highlight_null() # Esimerkkidatassa neljän viimeisen sarakkeen osalta puuttuvia arvoja sisältävien rivien poistaminen ei tule kyseeseen, koska dataa ei tämän jälkeen jäisi jäljelle. # # Seuraavassa poistan rivit, joilla on puuttuvia arvoja muuttujissa 'koulutus', 'työtov' ja 'palveluv' sekä täydennän neljän viimeisen muuttujan puuttuvat arvot nolliksi. Näin toimimalla menetän datasta 3 riviä. # In[6]: df1 = df.dropna(subset=['koulutus', 'työtov', 'palveluv']) df1 = df1.fillna({'työterv':0, 'lomaosa':0, 'kuntosa':0, 'hieroja':0}) # Katson kuinka monta riviä jäi jäljelle df1.shape[0] # Seuraavassa täydennän kaikki puuttuvat arvot, jolloin dataan jää alkuperäinen määrä rivejä. Eri muuttujille käytän erilaisia korvaamismenetelmiä (mediaani, keskiarvo, 0). # In[7]: df2 = df.fillna({'koulutus': df['koulutus'].median(), 'työtov': df['työtov'].mean(), 'palveluv': df['palveluv'].mean(), 'työterv':0, 'lomaosa':0, 'kuntosa':0, 'hieroja':0}) # Katson kuinka monta riviä jäi jäljelle df2.shape[0] # ## Kategoristen muuttujien muuntaminen dummy-muuttujiksi # # Pandas-kirjaston get_dummies-toiminto muuntaa kategoriset muuttujat dummy-muuttujiksi. # # https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.get_dummies.html # # Esimerkiksi sukup-muuttuja saa arvoja 1 (mies) ja 2 (nainen). get_dummies-toiminto tekee sukup-muuttujasta muuttujat sukup_1 ja sukup_2. Jos kyseessä on mies, niin sukup_1-muuttujan arvo on 1. Jos kyseessä on nainen, niin sukup_2-muuttujan arvo on 1. # # Seuraavassa muunnan sukup-, perhe- ja koulutus-muuttujat dummy-muuttujiksi: # In[8]: df_dummies = pd.get_dummies(data=df2, columns=['sukup', 'perhe', 'koulutus']) # Katson datan muuttujat df_dummies.info() # ## Standardointi # # Jos muuttujat ovat suuruusluokaltaan erilaisia, niin muuttujien skaalauksella voidaan joissain tapauksissa päästä parempiin malleihin. Standardointi on paljon käytetty skaalausmenetelmä. Standardoinnissa muuttujan arvot muunnetaan normaalijakauman z-pisteiksi. Z-piste ilmoittaa kuinka monen keskihajonnan päässä muuttujan arvo on kaikkien arvojen keskiarvosta. # # Standardoinnin voin toteuttaa sklearn.preprocessing-kirjastosta tuodulla StandardScaler-toiminnolla. # # https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html # In[9]: from sklearn.preprocessing import StandardScaler scaler = StandardScaler() # Tässä standardoin iän, palkan ja palveluvuodet df2[['ikä', 'palkka', 'palveluv']] = pd.DataFrame(scaler.fit_transform(df2[['ikä', 'palkka', 'palveluv']])) df2 # ## Poikkeavat arvot # # Poikkeavina arvoina voidaan pitää arvoja, jotka olisivat normaalijakaumassa epätodennäköisiä. Tällaisia arvoja sisältävien rivien poistaminen voi joissain tapauksissa parantaa mallia. Poikkeavien arvojen poistamisen mielekkyys riippuu monista seikoista ja on harkittava kussakin tapauksessa erikseen. # # Poistaminen voidaan tehdä z-pisteiden (standardoitujen arvojen) perusteella. Z-piste ilmoittaa kuinka monen keskihajonnan päässä arvo on kaikkien arvojen keskiarvosta. Usein rajana käytetään arvoa 3: jos muuttujan arvo on yli kolmen keskihajonnan päässä keskiarvosta, niin se poistetaan. # # Lisätietoa poikkeavien arvojen poistamisesta: # # https://stackoverflow.com/questions/23199796/detect-and-exclude-outliers-in-pandas-data-frame # # Seuraavassa lasken normaalijakauman todennäköisyyden itseisarvoltaan yli kolmen (3) suuruisille z-pisteille: # In[10]: from scipy import stats print('Todennäköisyys sille, että arvo on yli kolmen keskihajonnan päässä keskiarvostaan', 2*stats.norm.cdf(-3)) # Edellä standardoin df2:n palkan. Katsotaan viisi suurinta ja pienintä z-pistettä: # In[11]: df2['palkka'].nlargest(n=5) # In[12]: df2['palkka'].nsmallest(n=5) # Yllä olevan mukaisesti poistettavaksi joutuisivat kaksi suurinta palkkaa, joiden z-pisteet ovat suurempia kuin 3. # # Poistaminen sujuu yhdellä koodirivillä. Seuraava koodi toimii vaikka z-pisteitä ei olisi dataan ennestään laskettukaan: # In[13]: df3 = df2[(np.abs(stats.zscore(df2))<3).all(axis=1)] # Katson kuinka monta riviä jäi jäljelle df3.shape[0] # Tässä tapauksessa poikkeavien arvojen poistaminen johti ainoastaan kahden rivin poistamiseen. # # Jos olet tottunut käyttämään lambdaa, niin edellisen voi tehdä myös seuraavasti (tässä lasken z-pisteet ilman stats.zscore()-toimintoa): # In[14]: df4 = df2[df2.apply(lambda x: np.abs(x-x.mean())/x.std()<3).all(axis = 1)] # Katson kuinka monta riviä jäi jäljelle df4.shape[0] # ## Logaritmimuunnos # # Muuttujien normaalijakaumasta poikkeavia jakaumia on mahdollista korjata lähemmäksi normaalijakaumaa muuttujien muunnoksilla. Paljon käytetty muunnos vinon jakauman korjaamiseen on logaritmien ottaminen. # # Seuraavassa muunnan palkka-muuttujan arvot logaritmeikseen. Histogrammilla voin nopeasti tarkistaa korjaantuiko jakauma lähemmäksi normaalijakaumaa. # In[15]: df['palkka_log'] = np.log(df['palkka']) # Katsotaan muuuttujien histogrammit df[['palkka', 'palkka_log']].hist() # Logaritmia ei voi ottaa nollasta. Tämän vuoksi esimerkkidatan palveluvuosiin en voi käyttää logaritmimuunnosta. Jos lisään palveluvuosiin yhden vuoden (jolloin muuttuja ilmoittaa kuinka monetta vuotta henkilö palvelee yrityksessä), niin logaritmimuunnos onnistuu: # In[16]: df['palveluv_log'] = np.log(df['palveluv']+1) df[['palveluv', 'palveluv_log']].hist() # Muuttujien muunnoksia käytetään eri tarkoituksiin ja muunnokset ovat laaja ja monitahoinen aihe. Logaritmin ohella paljon käytettyjä muunnoksia ovat neliöjuuri, toiseen potenssiin korottaminen, käänteisluku jne. # ## Datan tasapainottaminen # # Luokittelumalleja käytettäessä opetusdata kannattaa tasapainottaa jos jokin luokista on selvästi aliedustettuna. Tasapainottamisen voi tehdä monella tavalla. Katso https://towardsdatascience.com/5-techniques-to-work-with-imbalanced-data-in-machine-learning-80836d45d30c # # Helppo tapa tasapainoltukseen on käyttää **imbalanced-learn** -kirjastoa: https://imbalanced-learn.org/stable/. # Kirjaston voi asentaa Anacondaan komentoriviltä (Anaconda prompt) komennolla: # # `conda install -c conda-forge imbalanced-learn` # In[17]: # Jos df2:n kuntosalin käyttöä mittaava muuttuja olisi kohdemuuttujana # niin käyttäjien määrä on aliedustettuna df2['kuntosa'].value_counts() # In[18]: # Tuodaan RandomOverSampler from imblearn.over_sampling import RandomOverSampler # Selittävät muuttujat X = df2.drop('kuntosa', axis=1) # Kohdemuuttuja y = df2['kuntosa'] # Tasapainotus ros = RandomOverSampler(random_state=2) X, y = ros.fit_resample(X, y) # Tarkistetaan kohdemuuttujan jakauma pd.DataFrame(y).value_counts() # ## Lisätietoa # # Data-analytiikka Pythonilla: https://tilastoapu.wordpress.com/python/