Labtutorials.org

AWK trükkök használata bioinformatikai problémák megoldására

In bioinformatics, molecular biology on August 1, 2021 at 8:00 am

Szerző: Dr. Nagy Gergely

Az AWK

A közel 50 éves AWK programnyelv eléggé hasznosnak bizonyult ahhoz, hogy ne kopjon ki a közhasználatból. Ehhez hozzájárult a bioinformatika is, amely a különböző táblázat jellegű adattömbök feldolgozása/átalakítása során a mai napig használja a programnyelv nyújtotta lehetőségek széles tárházát. Az AWK nevét a kidolgozói – Alfred Aho, Peter Weinberger és Brian Kernighan – vezetékneveinek kezdőbetűiről kapta, és nem véletlenül hívják programnyelvnek, nemcsak programnak. Az AWK „program jellegét” az adja, hogy az egyszerűbb, csupán kapcsolókkal, paraméterekkel rendelkező UNIX parancsokhoz hasonlóan egyszerűen UNIX parancssorokba, szkriptekbe ágyazható. Emellett viszont saját szintaktikával, változókkal, ciklusokkal és a különböző tömbök („array” és „hash”) használatának lehetőségével is fel van fegyverezve.

Ebben a bejegyzésben viszonylag összetett feladatok nagyon egyszerű megoldását szeretném bemutatni AWK hash-ek felhasználásával. A hash-ek olyan tömbök, amelyek esetében egy azonosítóhoz több érték is tartozhat. Előfordulhat például, hogy génexpressziós adatok kezelése során egy génhez több expressziós érték is tartozik, mi viszont minden egyes génhez egyetlen értéket szeretnénk rendelni. Microarray esetében maga a technika jellege, az egyes gének több próbaszettel való reprezentálása eredményezi azt, hogy egy génhez több expressziós érték is tartozik, RNA-seq esetén viszont minden, a génekről mérhető mennyiségben átíródó alternatív transzkriptum expressziós értéke kiszámolható. Hogy az egy génhez tartozó expressziós értékek közül melyiket használjuk, pl. a legalacsonyabbat, legmagasabbat, vagy éppen az összes átlagát, az génfüggő, ill. szubjektív, de az AWK lehetőséget biztosít mindezek meghatározására/kiszámolására.

Táblázat létrehozása a későbbi számításokhoz

Az alábbi példákban egy egyszerű táblázaton szeretném bemutatni az AWK használatát, de a parancssorok bármekkora táblázaton működnek (a UNIX rendszerekben ugyanis nincs memóriakorlát), akár többmillió soros is lehet a bemeneti fájl. Az oszlopszám inkább jelent korlátot, de soron belüli ciklust, sőt transzponálást is lehet csinálni AWK-ban. Most csak egy kétoszlopos táblázatot készítünk, de ha ennél több oszlopunk van, csak az oszlopok sorszámát kell majd átírni a megfelelőre, és működik is a parancs.

A táblázat létrehozásához az „echo” parancsot használjuk, amely kilistázza, amit beadunk neki. Ez önmagában nem tűnik nagy dolognak, de ha manuálisan készítünk táblázatot tabulátor és sorvég karakterekkel, elkerülhetetlen. Az „-e” kapcsoló a „\” jellel jelölt, ún. reguláris kifejezések megjelenítését szolgálja, jelen esetben az „új sor” jel („\n”) kiíratását, és a macskakörmökre is ezért van szükség.

echo -e “cat 3\ncat 4\ndog 7\ncat 2\ndog 12\ndog 2”

A kimenet pedig ez az azonosítókat és számokat tartalmazó táblázat:

cat 3
cat 4
dog 7
cat 2
dog 12
dog 2

Ha méginkább táblázatszerű kimenetet szeretnénk, az üres karakterek (space-ek) lecserélhetőek „\t”-re is, amely a tabulátort jelképezi, de parancssorban sokszor így is megteszi, és kevesebbet gépeltünk, ami előnyt jelent hosszútávon. (Részben ezért is szerepel a kutya és macska szó angolul a táblázatban.)

Tegyük fel, hogy a számok a szomszédságunkban elő kutyák és macskák kerekített súlyát jelképezik kg-ban (természetesen nem jelentenek problémát a törtek sem az AWK számára), és kíváncsiak vagyunk, hogy átlagosan mennyivel nagyobbak a kutyák, mint a macskák.

Az AWK szintaktikája – azonosítónkénti átlagszámítás

Ahogy a legtöbb UNIX parancs, az AWK is utoljára kéri a bemeneti fájl nevét, de erre most nincs szükség: táblázatunk a memóriából érkezik olvasásra a „csővezeték” / „|” karakteren keresztül (lásd lent a parancssor 1. sorának végét). Az AWK-ot kisbetűkkel hívjuk meg parancssorban, és a hozzá tartozó parancsok egyenes aposztróf jelek (léteznek jobbra és balra dőltek is) közé kerülnek, a tagolást kapcsos zárójelek határozzák meg (egyszerűbb esetben egy pár elég), a parancsok felsorolása esetén pedig pontosvesszőt használunk (2. sor). Az AWK paraméterei, amelyek maga a program (awk) és az első aposztróf jel közé kerülnek kötőjellel ellátva, most hiányoznak; ilyenek lehetnek a mezőelválasztót és a külső változókat meghatározó „-F”, ill. „-v” paraméterek, de esetünkben az üres karakter felismerhető (a tabulátor sem jelentene problémát), és nincs szükség „külső” változó megadására.

Fontos megjegyezni, hogy sok fájlformátum megköveteli a fejléc meglétét, amelyet az AWK ugyancsak képes kezelni. A megkezdett kapcsos zárójelek előtt (és kizárólag a külső aposztróf jelek között) az „NR” változó beállításával meghatározható, hogy melyik sorokra vonatkozzanak a kapcsos zárójelben lévő parancsok. (A kapcsos zárójeleken belül ugyanez „if” feltétellel oldható meg.) Jelen esetben ettől is eltekinthetünk, mert nincs fejléc.

Végül elérkeztünk az első pár kapcsos zárójelhez, amely meghatározza, mi legyen a hash-ben (2. sor eleje). Az „n” hagyományosan darabszámot, ill. sorszámot jelent; ebben az esetben valójában mindkettőt. Ha szögletes zárójelben az azonosító oszlopszáma (amire mindig dollárjellel hivatkozunk) követi („n[$1]”), akkor az adott azonosítóhoz tartozó értékek számát reprezentálja. A „++” karakterek a ciklus definiálásának részei. A pontosvessző után azt határozzuk meg, hogy az „x” változó az egyes azonosítókhoz ($1) tartozó értékek ($2) összegét tartalmazza („+=”). A ciklusok során ezáltal egy folyamatosan növekvő, változó, de egyszerű tömbhöz jutunk. Az „END” (2. sor közepe) éppen ezért azt szolgálja, hogy a továbbiakban csak a végösszegekkel foglalkozzunk, ne írassunk ki minden köztes állapotot. A parancsok második fele egy olyan ciklust definiál, amely nem soronként, hanem azonosítónként (az azonosító sorszáma, „n” alapján) halad – az „i” változó felveszi minden azonosító értékét, a „print” paranccsal pedig kiíratjuk az „i” azonosítók mellett az „x[i]” összegek és „n[i]” elemszámok hányadosát, tehát az állatok átlagos tömegét (2. sor vége).

1.sor$         echo -e “cat 3\ncat 4\ndog 7\ncat 2\ndog 12\ndog 2” |
2.sor$         awk ‘{n[$1]++; x[$1] += $2} END {for (i in n) print i,x[i]/n[i]}’

Az eredmények tehát így alakulnak:

cat 3
dog 7

Fejléc jelenlétében vagy bonyolultabb, többoszlopos táblázat esetében természetesen ki kell egészíteni a parancsot, ill. át kell írni az oszlopszámokat az azonosítók és értékek alapján, de a kód így sem lesz olyan hosszú, hogy tördelést igényeljen, ill. szkriptbe kelljen írni.

Az azonosítónkénti maximális értékek kinyerése

A következő kérdés az volt, hogy hogyan alakul az állatok minimális vagy maximális súlya. Ehhez egy újabb UNIX parancs segítségül hívása is szükséges, ha a korábbihoz hasonló AWK formulát szeretnénk használni. Ez a parancs a „sort”, amely arra alkalmas, hogy valamelyik oszlop vagy oszlopok alapján rendezze a sorokat (lásd az alábbi parancssor 2. sorát). Jelen esetben a 2. oszlop alapján szükséges a sorba rendezés, mivel a használt AWK formula mindig csak a legutolsó sort tartja meg egy adott azonosítóhoz. Ha a kis értékek felől haladunk a nagyok felé, a legnagyobb tömegek lesznek az eredmények között, és fordítva. A „-k2,2n” kifejezés azt jelenti, hogy kizárólag a 2. oszlop alapján történjen a sorba rendezés és numerikusan („n”), számértékek, nem abc-rend alapján, növekvő sorrendben. Ellentétes sorrendet az „r” (reverz) paraméterrel érhetünk el, amit folytatólagosan kell a „-k2,2n” után írni.

Az AWK parancsok annyiban változnak, hogy az „x[$1]” változó minden egyes ciklusban felveszi az azonosítóhoz tartozó teljes sor értékét a „$0” speciális változó segítségével, így végül az „x[i]” már magában foglalja mind az azonosítót, mind pedig a maximális értéket (3. sor).

1.sor$         echo -e “cat 3\ncat 4\ndog 7\ncat 2\ndog 12\ndog 2” |
2.sor$         sort -k2,2n |
3.sor$         awk ‘{n[$1]++; x[$1] = $0} END {for (i in n) print x[i]}’

Az eredmény pedig azt mutatja, hogy a legnagyobb kutya háromszor olyan nehéz, mint a legnagyobb macska a szomszédságban:

cat 4
dog 12

Azonosítónként minden érték kiíratása

Ahhoz, hogy teljes képet kapjunk a súlyeloszlásokról, jó ránézni egyszerre minden értékre; még jobb, ha azok sorba is vannak rendezve – ez már a „sort” segítségével meg is történt (lásd az alábbi parancssor 2. sorát). Egy kisebb technikai (inkább esztétikai) probléma miatt emellett szükség van egy újabb parancsra, a „sed”-re (4. sor). Ez megintcsak több egy átlagos parancsnál, mivel egy UNIX parancssoros szövegszerkesztőről beszélünk, amely a nevét is innen kapta (stream editor), és lényegében ugyancsak beágyazható bármely parancssorba. A „sed” esetében az egyenes aposztróf helyett macskakörmök is határolhatják a parancsokat; utóbbiak megengedik a reguláris kifejezések felismerését (bár itt ilyenek most nincsenek). Jelen esetben egy fölösleges, üres karakter utáni vesszőt kell majd eltüntetni minden sorból, ami a program nyelvén annyit tesz, hogy egy üres karaktert és egy vesszőt kicserélünk egyetlen üres karakterre. A csere (szubsztitúció) „s” jelét három „/” karakter követi, melyek magunkban foglalják előbb a keresett mintázatot (az első két „/” között), majd a célmintázatot (az utolsó két „/” között). A „/” jelek igény szerint bármilyen egyéb karakterre cserélhetőek, pl. ha „/” jelet is érint a csere.

Az AWK formula annyiban változik, hogy nem átlagolunk, hanem konkatenálunk, ami most azt jelenti, hogy vesszővel elválasztva minden érték bekerül előbb az „ x[$1]”, majd az „x[i]” változókba (3. sor). Ebben az esetben ismét szükség van az „i” kiíratására, mert csak a második oszlop értékei lettek egymás után fűzve (+ egy vessző az értékek sora előtt, amit végül „sed”-del tüntetünk el).

1.sor$         echo -e “cat 3\ncat 4\ndog 7\ncat 2\ndog 12\ndog 2” |
2.sor$         sort -k2,2n |
3.sor$         awk ‘{n[$1]++; x[$1] = x[$1]”,”$2} END {for (i in n) print i,x[i]}’ |
4.sor$         sed “s/ ,/ /”

Az eredmény megmutatja, hogy a szomszéd kutyák jelentős méretkülönbségeket mutatnak, az egyikük pl. kisebb egy átlagos macskánál is, pedig már kifejlett egyed.

cat 2,3,4
dog 2,7,12

A parancssorok pedig azt mutatják meg, hogy minimális befektetéssel, szűk egy sor begépelésével komplex feladatok oldhatók meg, pl. a „hash-típusú” – és nem feltétlenül génexpressziós – adatok gyors szűrése és feldolgozása; és a feltételekről és bonyolultabb számításokról még nem is beszéltünk.

Az Emberi Erőforrások Minisztériuma ÚNKP-20-5-DE-276 kódszámú
Új Nemzeti Kiválóság Programjának támogatásával készült.