<jupyter_start><jupyter_text>Pandase sissejuhatusSelles vihikus ma näitan põgusalt kuidas teha lihtsamat andmeanalüüsi Pythoni vihikus kasutades Pandast ja vähemal määral ka Numpyt.[Pandas](http://pandas.pydata.org/) on Pythoni andme analüüsi teek mis pakub mitmeid mugandusi tavaliste andmeanalüüsi käigus vajaminevate protseduuride tegemiseks. Pandas ise on üles ehitatud suures osas [Numpy](http://www.numpy.org/)-l mida kasutatakse Pythonis keerulisemate arvutuste tegemiseks (arvutused maatriksitega jne). Numpy-t ma antud vihikus väga palju ei tutvusta. Et kasutada Pandast tuleb see importida. Importimisel Python laeb mällu `pandas` ja `numpy` teegid et neid saaks kasutada.<jupyter_code>import numpy as np    #Numpy import
import pandas as pd   #Pandase import

# jooniste kuvamiseks hiljem tuleb sisestada see rida...
%matplotlib notebook<jupyter_output><empty_output><jupyter_text>DataFrame`DataFrame` on Pandase üks põhilisi andmestruktuure. Põhimõttelised võib seda võtta nagu Exceli tabelit. DataFrame saab luua Pandase meetodiga `.DataFrame` edastades sellele *dictionary*.Andmed on paigutatud Pandase `DataFrames`-is tulpadesse. Andmed on indekseeridud rea kaupa. Kui indeks on määramata siis sellek on lihtsalt rea number.<jupyter_code>test_dict = {
    'samplename': ['Sample1A' ,'Sample1B' ,'Sample1C',
                   'Sample2A' ,'Sample2B' ,'Sample2C',
                   'Sample3A' ,'Sample3B' ,'Sample3C',
                   'Sample4A' ,'Sample4B' ,'Sample4C',],
    'konstrukt' : ['plasmiid1','plasmiid1','plasmiid1',
                   'plasmiid1','plasmiid1','plasmiid1',
                   'plasmiid2','plasmiid2','plasmiid2',
                   'plasmiid2','plasmiid2','plasmiid2'],
    't88tlus' :   ['dmso','dmso','dmso',
                   'aine','aine','aine',
                   'dmso','dmso','dmso',
                   'aine','aine','aine'],
    'luc2p' : [ 450, 543, 243,
                456, 644, 453,
               1234,1342,1532,
               4500,6453,4800]}
df = pd.DataFrame(test_dict)
df<jupyter_output><empty_output><jupyter_text>`DataFrame`-i loomisel saab ette ka anda tulpade nimede järjekorra. Muidu tuleb see tähestikulises järjekorras (vist).<jupyter_code>df = pd.DataFrame(test_dict,
                  columns = ['samplename','konstrukt','t88tlus','luc2p'])
df<jupyter_output><empty_output><jupyter_text>`DataFrame`-il on andmed paigtatud ridadesse `index`-i järgi. Kui seda pole määratud siis on selleks lihtsalt järjekorra number. Samas on hea kui andmed on paigutatud `DataFrame`-i ridadesse meile endale kasuliku tähistuse järgi. Näiteks proovide nimede järgi:<jupyter_code>df = pd.DataFrame(test_dict,
                  columns = ['konstrukt','t88tlus','luc2p'],
                  index = test_dict['samplename'])
df<jupyter_output><empty_output><jupyter_text>Juba tehtud DataFrame-ile saab indeksit luua meetodiga `set_index('tulp')`. See tagastab uue `DataFrame`-i millel on meie määratud tulp muudetud index-iks.<jupyter_code>df = pd.DataFrame(test_dict,
                  columns = ['samplename','konstrukt','t88tlus','luc2p'])
df<jupyter_output><empty_output><jupyter_text>Andmete laadimine failidestAndmeid saab laadida `DataFrame`-i ka failidest. Toetatud faili tüüpide nimekiri on pikk ja seda saab vaadata [Pandase dokumentatsioonist](https://pandas.pydata.org/pandas-docs/stable/io.html). Meile ühed tavalisemad ettejuhtuvad failid on Exceli (`.xlsx`) ja *comma separated values* (`.csv`) failid. Mõlemat suudab Pandas ilusti laadida.Exceli faili laadimiseks tuleb kasutada Pandase `.read_xlsx` meetodit:<jupyter_code>df = pd.read_excel("example_data/02-exp1.xlsx")
df<jupyter_output><empty_output><jupyter_text>Lihstalt faili nime andes laeb pandas sellest ainult esimese töölehe. Et saada kätte teisi tuleb anda `.read_excel` funktsioonile ka lehe nimi:<jupyter_code>df = pd.read_excel("example_data/02-exp1.xlsx", sheetname = "Sheet2")
df<jupyter_output><empty_output><jupyter_text>Andmete laadimise saab ära määrata ka millist tulpa võiks Pandas kasutada `index`-ina. Seda saab teha `index_col` argumendiga. Tulpade järjekord algab nullist.<jupyter_code>df = pd.read_excel("example_data/02-exp1.xlsx",
                   sheetname="Sheet2",
                   index_col = 0)
df<jupyter_output><empty_output><jupyter_text>Andmete väljavõtmine `DataFrame`-ist Lõike `DataFrame`-ist saab välja võtta meetoditega `.loc` ja `.iloc`. Esimene on selle jaoks et võtta välja lõike ridade ja tulpade nimede järgi. Teist saab kasutada võttes lõike välja nö kordinaatide järgi. All olevate näidete jaoks laen ma andmed exceli faili esimeselt lehelt:<jupyter_code>df = pd.read_excel("example_data/02-exp1.xlsx", index_col=0)
df
df[ ["konstrukt", "luc2p"] ]<jupyter_output><empty_output><jupyter_text>`.loc[ [read], [tulbad] ]`Selleks tuleb edastada sellele Pythoni *list* või *tuple* nimekirjad vastavate ridade või tulpadega mida tahetakse:```df.loc[ [... rea indexite nimekiri...] , [tulpade nimede nimekiri] ]```<jupyter_code>df.loc[ ['Sample1A', 'Sample2A'] , ['konstrukt','luc2p'] ]<jupyter_output><empty_output><jupyter_text>Asendades ühe või teise kooloniga `:` saab kätte kõik nende ridadega tulbad või siis vastavaltkõik vastavate tulpadega read.<jupyter_code>df.loc[ ['Sample1A', 'Sample2A'] , : ]
df.loc[ : , ['konstrukt','luc2p'] ]<jupyter_output><empty_output><jupyter_text>`.iloc[ [ ridade numbrid ], [ tulpade numbrid ] ]`Teine viis tulpades ja ridades olevatele andmetele ligi pääseda on kasutata `.iloc` meetodit.Selle meetodiga saab ligi andmetele kasutades tulpade ja ridade numbrilisi indekseid.<jupyter_code>df.iloc[ [0, 1] , [0, 1] ]
df.iloc[ [0, 1] , : ]
df.iloc[ : , [0, 1] ]<jupyter_output><empty_output><jupyter_text>Läbi `iloc` ja `loc` meetodi saab ka olemasolevates tulpades andmeid muuta.<jupyter_code>df
df_muudetud = df.copy()
df_muudetud.loc[["Sample1A"], : ] = 10000000
df_muudetud<jupyter_output><empty_output><jupyter_text>Arvutuste tegemine `DataFrame`-iga Arvutuste tegemine `DataFrame`-iga käib tulpade kaupa. Lihtsamateks arvutusteks ei pea kasutama tulpade andmetele ligipääsemiseks `.loc` või `.iloc` meetodeid vaid saab lihtsalt kasutada tulba nime index-ina. Kasutan jällegi näidisena Exceli failist esimese lehe andmeid:<jupyter_code>df_algne = pd.read_excel("example_data/02-exp1.xlsx", index_col=0)
df_algne<jupyter_output><empty_output><jupyter_text>Nii saab tulpadega teha standard arvutusi: korrutamine, jagamine jne.<jupyter_code># teen siin laetud exceli tabelist koopia lihtsalt eksperimenteerimiseks
df = df_algne.copy()
df
df["luc2p-10x"] = df["luc2p"] / 10
df<jupyter_output><empty_output><jupyter_text>Ning kasutada ka teiste tulpade andmeid arvutustes:<jupyter_code>df["luc2p-ratio"] = df["luc2p"] - df["luc2p-10x"]
df<jupyter_output><empty_output><jupyter_text>**Märkus/Hoiatus** - Kui te selliseid arvutusi teete siis tasub alati tulemus salvestada uude tulpa. Mitte kasutada tulemuse tulpa praeguses arvutuses või arvutada eelmise tulba väärtust üle. Seda sellepärast et siis hakkab arvutuse tulemus sõltuma sellest mitu korda on koodi läbi jooksutatud!!!<jupyter_code>df_vale = df.copy()
df_vale["luc2p-10x"] = df_vale["luc2p-10x"]*10
df_vale<jupyter_output><empty_output><jupyter_text>Tulpadega arvutuste jaoks annab kasutada ka Numpy funktsioone. Näiteks  * `np.log & np.log10`  * `np.pow`    * `np.exp`<jupyter_code># võtan värske `DataFrame`-i koopiast
df = df_algne.copy()
df["log-luc2p"] = np.log( df["luc2p"] )
df<jupyter_output><empty_output><jupyter_text>* `np.min`  * `np.max`  * `np.mean`  * `np.sdt` `np.min` ja `np.max` funktsioonidega saab kogu tulba miinimumi kasutada arvutustes. Niisama Pandase DataFrame-i sellesse funktsiooni panne santakse kõige väiksema väärtusega rida meile. Andes ette tulba antakse kogu tulba väikseim/suurim jne väärtus:<jupyter_code>print("Min: {}, Max:{} , Mean:{}".format( np.min(df["luc2p"]),
                                          np.max(df["luc2p"]),
                                          np.mean(df["luc2p"]) ) )
df<jupyter_output><empty_output><jupyter_text>Mean-centering<jupyter_code>df["luc2p-meancenter"] = df["luc2p"] - np.mean( df["luc2p"] )
df<jupyter_output><empty_output><jupyter_text>Näiteks saab teha kogu andmestikul andmete skaleerimise 0 ja 1 piiresse, nö *min-max* skaleerimine:\begin{equation}x_{minmax} = \frac{x - X_{min} }{X_{max} - X_{min} } \\\end{equation}<jupyter_code>df["luc2p-minmax"] = ( df["luc2p"] - np.min(df["luc2p"]) ) / ( np.max(df["luc2p"]) - np.min(df["luc2p"]) )
df<jupyter_output><empty_output><jupyter_text>Et seda skaleerimist oleks mugavam kasutada ka teiste tulpade puhul saab selle jaoks kirjutada funktsiooni:<jupyter_code>def minmax_scale(x):
    """Teisendab andmed 0 ja 1 vahele."""
    return ( x - np.min(x) ) / ( np.max(x) - np.min(x) )
df["minmax-norm-func"] = minmax_scale( df["luc2p"] )
df
df["minmax-norm-log"] = minmax_scale(df["log-luc2p"])
df<jupyter_output><empty_output><jupyter_text>Arvutused gruppidelEt teha arvutusi pandases gruppidel tuleb kasutada meetodit `.groupby`. Saadud gruppidel saab viia läbi lihtsamaid arvutusi kasutades pandase gruppidesse sisse ehitatud funktsioone `.mean(), std(), min(), .max, count()`. Need funktsioonid kõik tagastavad jällegi Pandase `DataFrame`-i. `.mean(), std(), min(), .max(), .count()`<jupyter_code>#praegune arvutustega DataFrame
df<jupyter_output><empty_output><jupyter_text>Need funktsioonid teevad arvutused kõigil tulpadel eraldi:<jupyter_code>df.groupby(['konstrukt']).mean()<jupyter_output><empty_output><jupyter_text>Kui me oleksime huvitatud ainult ühe tulba keskmistest siis enne grupeerimist peab selle välja valima:<jupyter_code>df[["konstrukt","luc2p"]].groupby("konstrukt").mean()<jupyter_output><empty_output><jupyter_text>Grupeerimist saab läbi viia ka mitmel tasemel. Näiteks grupeerida konstrukti ja töötluse järgi ning seejärel arvutada luc2P signaali keskmised:<jupyter_code>df.groupby( ['konstrukt','t88tlus'] ).mean()<jupyter_output><empty_output><jupyter_text>Teine võimalus saada kätte ennast huvitavaid andmeid on need hiljem saadud DataFrame-ist välja võtta:<jupyter_code>df_means = df.groupby(['konstrukt','t88tlus']).mean()
df_means<jupyter_output><empty_output><jupyter_text>Kasutades selleks niisama indeksi läbi pöördumist:<jupyter_code>df_means[ ["luc2p"] ]<jupyter_output><empty_output><jupyter_text>Praegu saadud `DataFrame`-il on index mitmetasandiline. See võib olla mõndadel juhtudel vajalik kuid et sellest lahti saada saab kasutada `.reset_index()` meetodit. Indeksi määramine käib jällegi meetodiga `.set_index()`.<jupyter_code>df_means
df_uus = df_means.reset_index()
df_uus
df_uus.set_index(['konstrukt'])<jupyter_output><empty_output><jupyter_text>`.agg` - aggregate Teine viis teha arvutusi gruppidel on gruppide `.agg` funktsioon.`.agg` käitub natuke erinevalt vastavalt sellele mis talle ette anda.Andes listi numpy funtsioonidega arvutab ta iga grupi sees vastava funktsiooni tulemuse:<jupyter_code>df.groupby(['konstrukt', 't88tlus']).agg( [ np.mean, np.std ] )<jupyter_output><empty_output><jupyter_text>Andes dictionary funktsioonidega siis kasutatakse dictionary võtmeid tulpadele määratud funktsioonide jaoks. Tagastatud `DataFrame`-il on ainult tulbad mille arvutusi sooviti:<jupyter_code>df.groupby(['konstrukt', 't88tlus']).agg( { 'luc2p' : np.mean } )<jupyter_output><empty_output><jupyter_text>Nii saab dictionarys sisestada ka mitut funktsiooni:<jupyter_code>df.groupby(['konstrukt', 't88tlus']).agg( {'luc2p' : [ np.mean, np.std ] })<jupyter_output><empty_output><jupyter_text>Ja ka ise kirjutatud funktsioone. Samas peavad kõik grupeerimisel kasutatavad funktsioonid olema redutseerivad, see tähendab et nende vastuseks peab olema üks kindel tulemus mis saadakse arvutusega grupi peal.<jupyter_code>def tobe_funktsioon(xgroup):
    """Funktsioon mis lihtsalt paneb grupi siseselt null väärtused"""
    return 0
def test_mean(xgroup):
    return np.mean(xgroup) / np.std(xgroup)
df.groupby(['konstrukt', 't88tlus']).agg({'luc2p': [ np.mean, np.std, tobe_funktsioon ]})<jupyter_output><empty_output><jupyter_text>Ka saab grupeerimisel teha midagi keerulisemat gruppidel. Kasutades `for` tsükklit saab `groupby` funktsioonilt kätte grupi nime ja grupi `DataFrame`-i.<jupyter_code>for name, df_group in df.groupby(["konstrukt"]):
    print(name)
    print(df_group)<jupyter_output>plasmiid1
            konstrukt t88tlus  luc2p  log-luc2p  luc2p-meancenter  \
samplename                                                          
Sample1A    plasmiid1    dmso    450   6.109248           -1437.5   
Sample1B    plasmiid1    dmso    543   6.297109           -1344.5   
Sample1C    plasmiid1    dmso    243   5.493061           -1644.5   
Sample2A    plasmiid1    aine    456   6.122493           -1431.5   
Sample2B    plasmiid1    aine    644   6.467699           -1243.5   
Sample2C    plasmiid1    aine    453   6.115892           -1434.5   

            luc2p-minmax  minmax-norm-func  minmax-norm-log  
samplename                                                   
Sample1A        0.033333          0.033333         0.187905  
Sample1B        0.048309          0.048309         0.245193  
Sample1C        0.000000          0.000000         0.000000  
Sample2A        0.034300          0.034300         0.191944  
Sample2B        0.064573          0.064573         0.297214  
Sam[...]<jupyter_text>Näide - kontrolli vastu normaliseerimine Üks kasulik asi tulpadega arvutusi tehes on see et Pandas teeb arvutusi tulpade vahel vastavalt ridade index-ile. See lubab meil index-iga mängides normaliseeriga andmeid kontrolli vastu.Arvutame kõigepealt katse sisesed keskmised:<jupyter_code>df_mean = df.groupby(['konstrukt', 't88tlus']).agg( {'luc2p':np.mean} )
df_mean<jupyter_output><empty_output><jupyter_text>Kuna meil keskmiste tabelil on mitmetasandiline index (konstrukt ja t88tlus) siis saame sellest lahti praegu:<jupyter_code>df_mean = df_mean.reset_index()
df_mean<jupyter_output><empty_output><jupyter_text>Kuna me tahame normaliseerida konstruktide siseselt siis peame index-i üles ehitama konstruktil:<jupyter_code>df_allmeans = df_mean.set_index('konstrukt')
df_allmeans<jupyter_output><empty_output><jupyter_text>Võtame esialgsest keskmiste tabelist välja ainult kontrolli väärtused kõigepealt muutes index-i "t88tlus"-eks, seejärel valides välja ainult kontrolli (ehk "dmso") väärtused. Siis eemaldades index-i ja muutes indeksi uuesti "konstrukt"-iks.<jupyter_code>df_mean.set_index('t88tlus')
df_mean.set_index('t88tlus').loc[ ["dmso"] , : ]
df_mean.set_index('t88tlus').loc[["dmso"],:].reset_index()
df_mean.set_index('t88tlus').loc[["dmso"],:].reset_index().set_index('konstrukt')<jupyter_output><empty_output><jupyter_text>Salvestame DataFrame-i normaliseerimise väärtustega eraldi<jupyter_code>df_control = df_mean.set_index('t88tlus').loc[["dmso"],:].reset_index().set_index('konstrukt')
df_control<jupyter_output><empty_output><jupyter_text>Nüüd on meil kaks `DataFrame`-i:  * `df_allmeans` - sisaldab kõiki meie katse keskmisi  * `df_control` - sisaldab ainult kontrolli (ehk "dmso") keskmisi<jupyter_code>df_allmeans
df_control<jupyter_output><empty_output><jupyter_text>Kuna arvutusi tehakse index-i spetsiifiliselt siis saab nüüd normaliseerida kummagi index-i rühma väärtused eraldi:<jupyter_code>df_allmeans["ctr-norm"] = df_allmeans["luc2p"] / df_control["luc2p"]
df_allmeans<jupyter_output><empty_output><jupyter_text>Andmete salvestamine ExcelisseOlles saanud kätte meid huvitava `DataFrame`-i saame selle salvestada excelisse meetodiga `.to_excel`:https://pandas.pydata.org/pandas-docs/stable/io.htmlio-excel-writer<jupyter_code>df_allmeans.to_excel("single-exp-means.xlsx", sheet_name="means")<jupyter_output><empty_output><jupyter_text>Kui me tahame salvestada mitut `DataFrame`-i tuleb seda teha eraldi kasutades Pandase ExcelWriter klassi:<jupyter_code>with pd.ExcelWriter("manysheets.xlsx") as writer:
    df_mean.to_excel(writer, sheet_name="means")
    df_allmeans.to_excel(writer, sheet_name="allmeans")<jupyter_output><empty_output><jupyter_text>Kogu katse andmete analüüsOlles teinud mitu katset on võimalik nende analüüs kokku võtta mitmel viisil. Kõige lihtsam oleks teha sarnaselt eelnevale katse sisene keskmiste arvutus ja normaliseerimine eraldi igal katseandmete `DataFrame`-il. Seejärel need andmed ühendada ja teha teha sarnane analüüs (koos ka t-testiga) ühendatud `DataFrame`-il.<jupyter_code>df_katse1 = pd.read_excel("example_data/02-exp1.xlsx", sheetname="Sheet1")
df_katse2 = pd.read_excel("example_data/02-exp1.xlsx", sheetname="Sheet2")
df_katse3 = pd.read_excel("example_data/02-exp1.xlsx", sheetname="Sheet3")
df_katse1_means = df_katse1.groupby(['konstrukt','t88tlus']).mean().reset_index().set_index('konstrukt')
df_katse1_means
df_katse1_control = df_katse1.groupby(['konstrukt','t88tlus']).mean().reset_index().set_index('t88tlus').loc["dmso",:].reset_index().set_index("konstrukt")
df_katse1_control
df_katse1_means["fold"] = df_katse1_means["luc2p"] / df_katse1_control["luc2p"]
df_katse1_means = df_katse1_means.reset_index()
df_katse1_means
df_katse1_means["katse"]="katse1"<jupyter_output><empty_output><jupyter_text>Sama moodi saab teha arvutused ära teistele katsete andmetele:<jupyter_code>df_katse2_means = df_katse2.groupby(['konstrukt','t88tlus']).mean().reset_index().set_index('konstrukt')
df_katse2_control = df_katse2.groupby(['konstrukt','t88tlus']).mean().reset_index().set_index('t88tlus').loc["dmso",:].reset_index().set_index("konstrukt")
df_katse2_means["fold"] = df_katse2_means["luc2p"] / df_katse2_control["luc2p"]
df_katse2_means = df_katse2_means.reset_index()
df_katse2_means["katse"]="katse2"
df_katse3_means = df_katse3.groupby(['konstrukt','t88tlus']).mean().reset_index().set_index('konstrukt')
df_katse3_control = df_katse3.groupby(['konstrukt','t88tlus']).mean().reset_index().set_index('t88tlus').loc["dmso",:].reset_index().set_index("konstrukt")
df_katse3_means["fold"] = df_katse3_means["luc2p"] / df_katse3_control["luc2p"]
df_katse3_means = df_katse3_means.reset_index()
df_katse3_means["katse"]="katse3"<jupyter_output><empty_output><jupyter_text>Nüüd on meil kolm erinevat tabelit katsesiseste normaliseeritud andmetega. Need saab omavahel ühendada kasutades pandase `.concat()` funktsiooni. Selle jaoks tuleb tabelid paigutada *list*-i ja edastada `.concat`-ile argumendina:<jupyter_code>df_koos = pd.concat( [ df_katse1_means, df_katse2_means, df_katse3_means ] )
df_koos<jupyter_output><empty_output><jupyter_text>Arvutamaks katsetevaheliste foldide keskmisi saab jälle kasutada group by-d:<jupyter_code>df_koos.groupby(['konstrukt','t88tlus']).agg( { "fold" : [np.mean, np.std] })<jupyter_output><empty_output><jupyter_text>Et teha t-testi arvutatud foldidel kasutame Scipy stats funktsiooni `ttest_rel`.https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.htmlscipy.stats.ttest_ind<jupyter_code>from scipy.stats import ttest_ind<jupyter_output><empty_output><jupyter_text>Sellega saab arvutada t-testi edastades sellele parameetritena kaks gruppi mida omavahel testitakse. Tagastatavas muutujas on p-value kirjas:<jupyter_code>test = ttest_ind([1.0, 1.0, 1.0 ],[10.0, 10.0, 100])
test.pvalue<jupyter_output><empty_output><jupyter_text>Et saaks t-testi jooksutada Pandase grupeeringutega võime selle jaoks kirjutada funktsiooni:<jupyter_code>def calc_p_value(x):
    #kontroll grupi väärtused
    ctr_group = x.size * [1.0]
    pval = ttest_ind(ctr_group, x)
    return pval.pvalue
df_all = df_koos.groupby(['konstrukt','t88tlus']).agg({"fold":[np.mean,
                                                      np.std,
                                                      calc_p_value]})

df_all<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11632984665380319, 'fraction_numerical': 0.02630242713516807, 'mean_word_length': 5.390330953926022, 'pattern_counts': {'":': 1, '<': 200, '<?xml version=': 0, '>': 200, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '50', 'dir': 'jupyter-structured-clean-dedup', 'id': '591535', 'license': 'CC-BY-4.0', 'n_tokens_mistral': 8453, 'n_tokens_neox': 8006, 'n_words': 1447, 'path': '02-pandas-introduction.ipynb', 'repo_name': 'kjaanson/jupyter-workshop-ttu', 'size': '19694'}