Közösen megoldott feladatok

A gyakorlatról

A gyakorlaton néhány egyszerű szövegbányászati eszközt próbálunk ki R-ben. A munkához hozzunk létre egy könyvtárat a gépen, amibe dolgozni fogunk. Lépjünk be a létrehozott könyvtárba egy terminállal és indítsuk el az R-t, az R utasítással.

A gyakorlat során főként a tm package-t fogjuk használni, ill. szükség lesz még további package-ekre is. A package-ek betöltéséhez adjuk ki a következő parancsokat:

(Amennyiben nincs telepítve valamelyik package, akkor először telepíteni kell amit a install.packages(“tm”, dependencies=TRUE), ill. install.packages(“SnowballC”, dependencies=TRUE) hívásokkal tehetünk meg - ehhez lehetséges, hogy sudo-val kell indítani az R-t.)

Fontos: ha a pdf fájlokból való beolvasás során (lásd. későbbi feladatok) a tm package megfelelő függvényének hívásakor olyan hibát kapunk, hogy a pdftotext parancs nem található, akkor telepíteni kell az Xpdf nevű programot, amely tartalmazza a keresett fájlt. (Ill. a telepítés után fel kell venni a keresett fájl elérési útvonalát a PATH-re, ha ez nem történne meg automatikusan.)

Hasznos: egy R-beli függvény help-jét a ?függény_név utasítással tudjuk előhozni. Itt megtudhatjuk, mire jó az adott függvény, hogyan kell használni, mik a paraméterei, valamint példákat is láthatunk a használatára. Pl.:

?mean

Adatok beolvasása

A tm package-ben a szövegek kezelésére szolgáló alapvető változótípus a Corpus. Ez egy absztrakt típus, melynek többféle konkrét leszármazottja van. A mérés során a VCorpus (Volatile Corpus) implementációjával fogunk dolgozni, melynek lényege, hogy az adatokat teljes egészében a memóriában tartja és kezeli. Így ha az objektumunkat megszüntetjük R-ben, akkor a Corpus nem áll rendelkezésre többé.

Egy VCorpus változót a VCorpus(x, readerControl) konstruktorral hozhatunk létre, ahol az x paraméter egy Source változót jelöl, ami a bemeneti fájlok helyét határozza meg. Többféle Source típus áll rendelkezésre, a mérésen a DirSource függvényt használjuk, amely segítségével a fájlrendszeren adhatunk meg egy könyvtárat, amely a bemeneti fájlokat tartalmazza.

A VCorpus konstruktor második paramétere a readerControl a fájlok beolvasását kontrollálja. A paraméter értéke egy lista kell, hogy legyen, amelynek az általunk használt esetben két eleme van: a reader nevű elemnek egy függvényt adunk meg, amely a bemeneti fájlokból szöveget csinál. Mivel mi pdf fájlokkal dolgozunk ezért a tm package-ben elérhető, erre a célra szolgáló readPDF() függvényt használjuk. A lista másik eleme, amit megadunk a nyelvet határozza meg, a listaelem neve language, és magyar nyelv esetén az értéke hun.

Ezek után az adatok beolvasását, az alábbi módon végezhetjük el (ügyeljünk arra, hogy az adatokat tartalmazó könyvtár útvonala helyes legyen!):

source <- DirSource("../../data/rejto_konyvek")
rejtoKonyvek <- VCorpus(x = source, readerControl = list(reader = readPDF(), language = "hun"))

Adatok megtekintése

Az adatokat a rejtoKonyvek nevű változóba olvastuk be, azonban a beolvasás után nem jelenik meg semmilyen információ a változónkról, nem nagyon tudjuk, hogy mi van a változóban. A változók “megnézésére” szolgál az str függvény, amely összefoglaló információkat jelenít meg a paraméterként megadott változóról:

str(rejtoKonyvek) # nagyon nagy kimenet is lehet sok dokumentum eseten

Láthatjuk, hogy a változó egy 5 elemű lista, amelynek minden eleme egy - beágyazott - 2 elemű lista, amelyek további elemeket tartalmaznak. Látható, hogy a kételemű listák egyik eleme a tartalom (content), míg a másik eleme metaadatokat tartalmaz. A lista egyes elemeire a “[[index]]”" operátorral hivatkozhatunk, ahol az index lehet pozitív egész szám (1-től kezdődően), vagy egy karakterstring, ami a listaelem nevét tartalmazza. (Azonban a listákban az elemek név nélkül is szerepelhetnek, ekkor az indexszel kell rájuk hivatkozni.) Ha névvel hivatkozunk egy listaelemre, akkor azt a $ operátorral is megtehetjük. Azaz, ha pl. az öt könyv közül az elsőnek a tartalmáról szeretnénk összefoglaló információkat kapni, akkor - többek között - az alábbi módokon tehetjük meg (ismét az str függvényt használva):

str(rejtoKonyvek[[1]][["content"]])
str(rejtoKonyvek[[1]]$content)

Néhány további függvény, amikkel különböző információkat tudhatunk meg a változóról:

rejtoKonyvek
length(rejtoKonyvek)
class(rejtoKonyvek)
typeof(rejtoKonyvek)

Adatok transzformációja

Az adatok transzformációjára a tm_map függvény szolgál a tm package-ben. Alapvető esetben két paramétere van a függvénynek: az első paraméter a corpus, míg a második paraméter a végrehajtandó transzformáció. A függvény a megadott transzformációt végrehajtja a corpus minden elemére. (Azaz esetünkben az 5 Rejtő-könyvre.)

Egyes gyakori transzformációkra a tm package-ben találhatunk már megírt függvényeket. Pl.:

  • Extra whitespace-ek eliminálása. (Ez a transzformáció a közvetlenül egymás után következő whitespace karakterek számát csökkenti 1-re, így a transzformációnak nincs mindig látványos eredménye.)
rejtoKonyvek <- tm_map(rejtoKonyvek, stripWhitespace)
  • Konvertálás kisbetűkre
rejtoKonyvek <- tm_map(rejtoKonyvek, content_transformer(tolower))

A transzformációra bármilyen TextDocument típussal visszatérő függvényt megadhatunk. A content_transformer függvény egy wrapperfüggvényt ad vissza, amely lehetővé teszi, hogy a legtöbb szövegmanipulációs függvényt (mint pl. tolower()) alkalmazhassuk transzformációként.

Metaadatok

Ahogy láttuk az adatok megtekintésénél (~str függvény), az egyes dokumentumokhoz tartoznak metaadatok. Az egyes dokumentumok metaadatainak halmaza (a metaadatok nevei) nem egyeznek meg szükségszerűen a corpus minden dokumentuma esetén.

A dokumentumokhoz közvetlenül tartozó metaadatok mellett az egész corpus is rendelkezhet metaadatokkal. Ezen belül kétféle metaadattípust különböztethetünk meg: a corpushoz is van lehetőség olyan metaadatokat rendelni, melyek különböző értékeket vehetnek fel az egyes dokumentumokra vonatkozóan (azonban ezek a metadatok minden dokumentum esetén rendelkeznek értékkel), ill. van lehetőség az egész corpusra vonatkozó metaadatok megadására is.

A metaadatokhoz hozzáférhetünk a korábban látott lista-indexelő módszerekkel vagy a meta() függvénnyel. Az egyes metaadattípusokat és használatukat az alábbi példa mutatja be:

# 1. A dokumentumokhoz tartozó egyéni metaadatok

# a második dokumentum metaadatai:
meta(rejtoKonyvek[[2]])
# új metaadat felvétele (vagy meglévő módosítása) a második dokumentumhoz:
# "megjegyzes" névvel egy új metaadatot veszünk fel, aminek értéke "Ujra kene olvasni" lesz
meta(rejtoKonyvek[[2]], "megjegyzes") <- "Ujra kene olvasni"
meta(rejtoKonyvek[[2]])

# 2. A corpus metaadatai
# 2.1. Corpus-hoz tartozo metaadatok dokumentumonkent mas ertekkel
meta(rejtoKonyvek)
meta(rejtoKonyvek, "osztalyzat") <- c(10,8,10,10,9)
meta(rejtoKonyvek)

# 2.2 Corpus kozos metaadatai
meta(rejtoKonyvek, type = "corpus")
meta(rejtoKonyvek, type = "corpus", "megjegyzes") <- "Kedvenc Rejto-konyvek"
meta(rejtoKonyvek, type = "corpus")

Dokumentum-kifejezés mátrix

Mátrix előállítása

Szövegbányászati feladatok során gyakran előfordul, hogy a dokumentumokból egy dokumentum-kifejezés mátrixot építünk az elemzésekhez. A tm package-ben a DocumentTermMatrix függvény szolgál erre, a létrehozott mátrix vizsgálatára pedig az inspect függvényt alkalmazhatjuk:

dokKifMx <- DocumentTermMatrix(rejtoKonyvek)
dokKifMx
inspect(dokKifMx[,1000:1010])

Műveletek

A tm package lehetőséget nyújt különböző műveletek végrehajtására az előállított mátrixon. Pl.:

  • Azon kifejezések megkeresése, amelyek legalább 50-szer előfordultak:
findFreqTerms(dokKifMx, 50)
  • Egy adott kifejezéssel (“légió”) korreláló (min. 0.95-ös korreláció) kifejezések megkeresése:
findAssocs(dokKifMx, "légió", 0.95)
  • A dokumentum-kifejezés mátrixok gyakran igen nagy méretűek lehetnek, ami megnehezítheti a különböző elemzési műveletek végrehajtását. A mátrix méretének csökkentésére ad lehetőséget a removeSparseTerms függvény, amely eltávolítja a mátrixból azokat a kifejezéseket, amelyek a dokumentumoknak egy kis százalékában fordulnak elő. Pl.:
# azon kifejezések eltávolítása amelyek kevesebb, mint a dokumentumok 60%-ában fordulnak elő
inspect(removeSparseTerms(dokKifMx, 0.4)[,1000:1010])
dokKifMxReduced <- removeSparseTerms(dokKifMx, 0.4)

Szótárak

Szövegelemzési feladatok során gyakran alkalmaznak szótárakat különböző feladatokhoz. Egy szótár szavak halmaza. A szótárakban általában valamilyen szempontból releváns szavakat gyűjtenek össze. A dokumentum-kifejezés mátrix létrehozásakor a DocumentTermMatrix függvény számára meg lehet adni egy szótárat is. Ekkor a mátrixba csak olyan szavak kerülnek be, amelyek szerepelnek a megadott szótárban. Pl.:

szotar <- c("igen", "nem")
dokKifMxSzotarral <- DocumentTermMatrix(rejtoKonyvek, list(dictionary = szotar))
inspect(dokKifMxSzotarral)

Műveletek data frame változóval

Konvertálás data frame-mé

Az eddig létrehozott dokumentum-kifejezés mátrix változó típusa a tm package-ben definiált DocumentTermMatrix. Ez lehetővé teszi bizonyos - a tm package-ben definiált - műveletek alkalmazását a mátrixon, melyek közül néhányat láttunk korábban, azonban megnehezíti néhány alapvető R funkció végrehajtását. Emiatt érdemes lehet a változót data frame-mé konvertálni, ahol a sorok lesznek az egyes dokumentumok, az oszlopok pedig a kifejezések. Az alábbi programrészlet létrehoz egy data frame változót a mátrixból, valamint megjelenít néhány információt a létrehozott data frame-ről:

dokKifDf <- as.data.frame(as.matrix(dokKifMxReduced))
ncol(dokKifDf)  # oszlopok száma
nrow(dokKifDf)  # sorok száma
names(dokKifDf) # oszlopok nevei
head(dokKifDf)  # oszlopok nevei és az első sorok

Ábrák

Miután rendelkezésre áll a dokumentum-kifejezés mátrix data frame formájában, sokféle különböző műveletet hajthatunk végre egyszerűen az adatainkkal.

  • Nézzük meg, hogy a kigyűjtött szavak gyakoriságainak eloszlását a Piszkos Fred, a kapitány könyvben. (Azaz annak az eloszlását szeretnénk felrajzolni, hogy a kigyűjtött szavak hányszor fordultak elő a könyvben.)
# nézzük meg hanyadik sor tartalmazza a Piszkos Fred, a kapitány könyv adatait
rownames(dokKifDf)      # a kimenetből látható, hogy az 5. sor
tmp <- as.numeric(dokKifDf[5, ])        # kiválasztjuk az 5. sort, és számmá konvertáljuk 
plot(density(tmp), xlab="Elofordulasok szama")

Látható, hogy egy-két szó nagyon sokszor előfordul, a legtöbb szó nagyon kevésszer. Nézzünk meg néhány statisztikát az eloszlásról. A summary függvény segítségével megkaphatjuk egy numerikus vektor elemeinek átlagát, mediánját, minimumát, maximumát, kvartiliseit.

summary(tmp)

A leggyakoribb szó 778-szor fordult elő, míg átlagosan 6-szor fordulnak elő a kigyűjtött szavak.

  • Ábrázoljuk oszlopdiagramon a “piszkos” szó előfordulásainak számát az egyes könyvekben:
barplot(dokKifDf[,"piszkos"], col=rainbow(5))
legend("topright", rownames(dokKifDf), fill=rainbow(5))

Önálló feladatok

  1. Adatok beolvasása

Az adatok most nem pdf fájlokban állnak rendelkezésre, hanem egyetlen csv fájlban, melyben minden sornak egyetlen eleme van: egy twitter-üzenet. A beolvasást, a corpus létrehozását az alábbi módon végezhetjük:

tweetsDf <- read.csv("../../data/tweetek/tweets.csv",
                     stringsAsFactors=FALSE, header=TRUE,
                     quote="", colClasses=c("character"),
                     sep="\n")
tweetsDf$text <- iconv(tweetsDf$text,"WINDOWS-1252","UTF-8")
sourceTwitter <- DataframeSource(tweetsDf)
tweets <- VCorpus(sourceTwitter, readerControl = list(language = "eng"))
  1. Hány dokumentum található a corpusban?

  2. Konvertáljuk kisbetűkre a corpusban található dokumentumokat!

  3. Végezzünk szótövezést a dokumentumokon!

Segítség: tm_map és stemDocument

Megjegyzés: Az alkalmazott szótövező algoritmus neve Porter stemming algorithm, bővebb információk az algoritmus működéséről: *http://snowball.tartarus.org/algorithms/porter/stemmer.html*.

  1. Szedjük ki a dokumentumokból a stopszavakat!

Segítség: tm_map és removeWords és stopwords(“eng”). A tm_map-nek megadott 3. paraméter automatikusan átadásra kerül a második paraméterben megadott függvénynek majd, annak hívásakor.

  1. Szedjük ki a dokumentumokból az extra whitespace-eket!

  2. Hozzunk létre a corpusból dokumentum-kifejezés mátrixot! Hány kifejezés került a mátrixba?

  3. Hozzunk létre olyan dokumentum-kifejezés mátrixot, amibe csak olyan szavak kerülnek, amelyek legalább 20-szor előfordulnak a corpusban! Hány kifejezés került így a mátrixba?

Segítség: findFreqTerms() használatával szótár építése, majd a szótár használata a mátrix felépítéséhez

  1. Hozzunk létre egy data frame változót az előző feladatban létrehozott dokumentum-kifejezés mátrixból!

  2. Ábrázoljuk annak az eloszlását, hogy a létrehozott data frame-ben szereplő kifejezések együttesen hányszor fordulnak elő az egyes twitter-üzenetekben. Hol van a minta mediánja?

Segítség: használjuk a rowSums és plot(density(…)) függvényeket. Hozzunk létre egy új oszlopot a data frame-ben, ami tartalmazza, hogy a kigyűjtött kifejezések összesen hányszor szerepelnek az adott üzenetben. Új oszlopot egy egyszerű értékadással tudunk létrehozni: data_frame_valtozo$uj_oszlop <- uj_oszlop_ertekeit_tartalmazo_vektor

Hivatkozások, hasznos oldalak

Függelék - Adatok gyűjtése

Tweetek

A twitter-üzenetek összegyűjtése az alábbi R programmal történt. (A program a twitter API-t használja, így szükség van autentikációra is, amihez kell egy twitter felhasználó, ami rendelkezik egy beregisztrált twitter-alkalmazással. Ezek után tudjuk megszerezni az autentikációhoz szükséges adatokat.)

library(twitteR)
api_key <- "SAJAT API KEY"
api_secret <- "SAJAT API SECRET"
access_token <- "SAJAT ACCESS TOKEN"
access_token_secret <- "SAJAT ACCESS TOKEN SECRET"
setup_twitter_oauth(api_key, api_secret, access_token, access_token_secret)
tweetsFromTwitter <- userTimeline("rbloggers", n=10000)
tweetsFromTwitter <- c(tweetsFromTwitter, userTimeline("rdatamining", n=10000))
tweetsFromTwitter <- c(tweetsFromTwitter, userTimeline("rogerfederer", n=10000))
tweetsFromTwitter <- c(tweetsFromTwitter, userTimeline("humansofny", n=10000))
tweetsFromTwitter <- c(tweetsFromTwitter, userTimeline("taylorswift13", n=10000))

tweetsFromTwitterDf <- do.call("rbind", lapply(tweetsFromTwitter, as.data.frame))
tweetsFromTwitterDf$text <- gsub("\n", " ", tweetsFromTwitterDf$text)
write.table(
        iconv(tweetsFromTwitterDf$text,"WINDOWS-1252","UTF-8"),
        "../../data/tweetek/tweets.csv",
        row.names=FALSE,
        col.names="text",
        quote=FALSE)