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.:
?meanA 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"))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 esetenLá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)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.:
rejtoKonyvek <- tm_map(rejtoKonyvek, stripWhitespace)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.
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")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])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.:
findFreqTerms(dokKifMx, 50)findAssocs(dokKifMx, "légió", 0.95)# 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ö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)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ő sorokMiutá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 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.
barplot(dokKifDf[,"piszkos"], col=rainbow(5))
legend("topright", rownames(dokKifDf), fill=rainbow(5))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"))Hány dokumentum található a corpusban?
Konvertáljuk kisbetűkre a corpusban található dokumentumokat!
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*.
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.
Szedjük ki a dokumentumokból az extra whitespace-eket!
Hozzunk létre a corpusból dokumentum-kifejezés mátrixot! Hány kifejezés került a mátrixba?
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
Hozzunk létre egy data frame változót az előző feladatban létrehozott dokumentum-kifejezés mátrixból!
Á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
R tutorial (alapoktól egészen sok minden): *http://cran.r-project.org/doc/manuals/r-release/R-intro.html*
tm package: *http://cran.r-project.org/web/packages/tm/index.html*
tm package-hez bevezető (a gyakorlat anyagának az alapja): *http://cran.r-project.org/web/packages/tm/vignettes/tm.pdf*
R-es eszközök NLP-hez: *http://cran.r-project.org/web/views/NaturalLanguageProcessing.html*
A felhasznált Rejtő-könyveket az alábbi linkekről lehet letölteni:
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)