Melyik átlagoló függvény legyen a választás: mean() vagy fmean()?

Ha egy adatsor számtani középértékét akarjuk meghatározni, akkor erre a célra használhatjuk a szabványos könyvtár statistics moduljának mean() és fmean() függvényeit. Az átlagolandó adatokat véges számú elemet szolgáltató iterálható objektumként kell megadni a függvényeknek.

Kérdésként merülhet fel, hogy mi a két függvény között a különbség, melyiket válasszuk?

A Python hivatalos dokumentációja szerint az fmean() az adatsor értékeit float típusú számmá konvertálja és az eredmény is float típusú lesz. Gyorsabb számítást eredményez, mint a mean(). E két jellemző miatt szerepel a név elején az f betű, ami egyaránt utalhat a float-ra és a fast (gyors) szóra is. Egyébként a Python 3.11 verziótól kezdve rendelkezik egy opcionális weights paraméterrel is, amellyel lehetővé válik, hogy súlyozott számtani átlagot számítsunk.

A mean() tekintetében azonban a hivatalos dokumentáció meglehetősen szűkszavú. Azt ugyan rögtön lehet látni, hogy ez nem rendelkezik az opcionális weights paraméterrel, de ami a visszaadott átlag típusát illeti,  csak az ott szereplő néhány egyszerű példakódból lehet következtetni, hogy a mean() nem csak float típusú eredményt tud szolgáltatni. De, hogy pontosan mikor, milyen esetben kapunk int, float, Fraction vagy Decimal típusú eredményt arról nem kapunk tájékoztatást. Minthogy arról a fontos jellemzőről sem, hogy a mean() pontosabb számítást eredményez, mint az fmean(), és ezért is lassabb. Mindezt csak a forráskód tanulmányozása és egyéni összehasonlító tesztek alapján tudhatjuk meg.

E jellemzőket fogjuk feltárni és alaposabban megismerni a továbbiakban, de nem a forráskód elemzésével, hanem úgy, hogy annak elvi megközelítése alapján egyszerűsített formában leutánozzuk a mean() függvény működését egy custom_mean() nevű függvénnyel. Az implementáció egyszerűsítését az jelenti, hogy a custom_mean() az adatsort csak konténerben tudja fogadni ellentétben a mean() függvénnyel, amelynek bármilyen iterálható objektum megadható argumentumként.

Ezek után a custom_mean() függvény megvalósításakor a mean() függvény két lényegi működési részletére kell csak összpontosítani. Az egyik, hogy a kiszámított és visszaadott középérték típusát hogyan kell meghatározni. A másik, hogy hogyan érhető el nagyobb számítási pontosság.

A mean() függvény visszatérési értékének típusa az adatsorban előforduló számtípusok közül azzal egyezik meg, amelyik a legtágabb számhalmazt reprezentálja. Ha az adathalmaz minden eleme int típusú egész szám, de az eredmény nem egész, akkor a visszaadott érték típusa float lesz. E szabályokat foglalja össze a következő ábra.

A pontosság növelése pedig úgy történik, hogy az átlagolandó számértékeket a közönséges törteket reprezentáló Fraction típusúvá alakítjuk, mert ekkor a közbenső műveletek tört számot jelentő eredményeinek esetleges kerekítési hibái kiküszöbölhetők. Ugyanakkor, ha az adatsor bármely értéke eleve olyan float típusú szám, amelynél ábrázolási pontatlanság lép fel, akkor azt ez a módszer természetesen nem tudja megszüntetni, csak a közbenső műveletek pontosságát tudja növelni.

Ezen elveknek és követelményeknek megfelelő custom_mean() függvény egy lehetséges megvalósítását láthatjuk alább. A működés megértését a részletes kommentek segítik.

Azt, hogy ez a függvény helyesen utánozza a mean() függvény működését a következő programsorokkal teszteljük.

Az eredményeket az alábbi ábrák mutatják. A helytakarékosság okán az egymás alá írt eredménysorokat két egymás melletti blokban tüntettük fel aszerint, hogy az eredmény egész vagy nem egész szám.

Amint az látható, a mean() és custom_mean() ugyanazt az eredményt adja azonos bemeneti adatsorozatra.

Most vizsgáljuk meg az alábbi tesztprogrammal, hogy futási idő tekintetében mit kapunk az fmean(), mean() és custom_mean() esetén.

Az eredmények visszaigazolják, hogy az fmean() jelentősen gyorsabb, mint a mean(). És azt is látjuk, hogy a saját készítésű custom_mean() számol a leglassabban.

Mindebből azt a következtetés lehet levonni, hogy a custom_mean() elsősorban arra jó, amire szántuk, vagyis a mean() függvény működésének megértésére, gyakorlati alkalmazásra kevésbé alkalmas egyrészt a futási idő miatt, másrészt azon korlát miatt, hogy nem adható meg tetszőleges iterálható objektum. A mean() és fmean() közötti választást az határozza meg, hogy mennyire számít a futási idő és a pontosság. Nagyméretű adatsor esetén, ahol a futási idő már érzékelhető mértékű, az fmean() használata lehet előnyösebb. Rövidebb adatsornál érdemes lehet a mean() alkalmazásának megfontolása, mert ekkor pontosabb eredményt kaphatunk. És, ha az eredmény nem float, akkor a pontosabb ábrázolást biztosító számtípust egy következő feldolgozási lépéshez továbbvihetjük.

Bár a mean() és fmean() pontosságbeli eltérését többször említettük, de azt még nem vizsgáltuk, hogy ez milyen mértékű. Ezt a következő függvénnyel teszteljük. Ennek célját a függvény dokumentációs karakterlánca (docstring) részletezi, működésének megértését pedig a kódban elhelyezett kommentek segítik.

A kiírt eredménysorokból érdemi következtetéseket vonhatunk le. Ha a példafuttatás szerint 1000 adatsorozatot vizsgálunk, akkor az esetek 25-40%-ában az fmean() és mean() nem ad azonos eredményt. És az is jól megfigyelhető, hogy minél hosszabb az adatsor, összességében annál nagyobb arányban mutat kisebb pontosságot az fmean() a mean() függvénnyel szemben.

Ebben a bejegyzésben a számos alapvető nyelvi elem mellett elsősorban a szabványos könyvtár statistics, random, decimal, fractions és math moduljainak osztályait és függvényeit használtuk. Mindezekről a Python tudásépítés lépésről lépésre című e-könyv „Készétel fogyasztás – a szabványos könyvtár moduljainak használata” fejezet „Matematikai számítások támogatása” című alfejezetében lehet részletesen olvasni.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.