2020年版(https://nlp100.github.io/ja/)をRで解いています。
R言語初心者です。もっと簡単な方法ありましたら是非教えてください。よろしくお願いします。
用語が間違っているかもしれません。すみません。編集リクエストをお願いします。
R言語(に関わらずPythonなどでも)の仕様を知って、慣れるのにもとても良い題材だと思います。作成者の方に感謝します。
関数の説明では、後ろの方の引数を省略してるものがあります。
第1章〜第4章(途中)です。(27、28は今後追加)
続きもやります。
R.version.string
# -> [1] "R version 3.6.3 (2020-02-29)"
ライブラリ
library(dplyr)
library(purrr) # 第1章
library(stringr)
library(jsonlite) # 第3章 問29
library(data.table) # 第4章
第1章 準備運動
00. 文字列の逆順
"stressed" %>% strsplit("") %>% unlist() %>% rev() %>% paste(collapse = "")
# -> [1] "desserts"
stri_reverse("stressed") # stringiライプラリを使用
# -> [1] "desserts"
strsplit(x, split)
文字列(のベクトル)xを文字列splitで区切り、リストを返す。
最初は、「なんでリスト返すん? unlistするのめんどいだけじゃん」って思ってたけど、Rでは、"stressed"も長さ1のベクトルなんだってことか。
paste(..., sep = " ", collapse = NULL)
ベクトルを与えて文字列に結合する場合は、collapse = "挟む文字列"
を指定する。
01. 「パタトクカシーー」
"パタトクカシーー" %>% strsplit("") %>% unlist() %>% .[c(T,F)] %>% paste(collapse = "")
# -> [1] "パトカー"
奇数番目・偶数番目
インデックスを[c(TRUE, FALSE)]
とするとベクトルから奇数番目を取り出せる。偶数番目は[c(FALSE, TRUE)]
。
02. 「パトカー」+「タクシー」=「パタトクカシーー」
c("パトカー", "タクシー") %>% strsplit("") %>% unlist() %>% matrix(nrow = 2, byrow = T) %>% paste(collapse = "")
# -> [1] "パタトクカシーー"
c("パトカー", "タクシー") %>% strsplit("") %>% {rbind(.[[1]], .[[2]])} %>% paste(collapse = "")
# -> [1] "パタトクカシーー"
ベクトル → 行列 → 文字列
matrix(data = NA, nrow = 1, ncol = 1, byrow = FALSE)
ベクトルから行列を作成する。行はrow、列はcolumn(カラム)。
デフォルトでは左のように入る。byrow = TRUE
にすると右のように入る。
c(1,2,3,4,5,6) %>% matrix(nrow = 2) c(1,2,3,4,5,6) %>% matrix(nrow = 2, byrow = T)
# [,1] [,2] [,3] # [,1] [,2] [,3]
# [1,] 1 3 5 # [1,] 1 2 3
# [2,] 2 4 6 # [2,] 4 5 6
nrowとncolのどちらかを指定すると、それを満たす行列が作成される。ベクトルの長さが7なのに、nrowを3とかにすると、余ったところに最初の値から入り出す。
nrowとncolの両方を指定すれば、入り切る分だけで作成される。足りなければ、これも最初から入る。
rbind(...)
与えられたベクトルを行ベクトルとして結合する。列ベクトルとして結合する場合はcbind関数を使う。
rbind(c(1,2,3,4), c(5,6,7,8))
# -> [,1] [,2] [,3] [,4]
# [1,] 1 2 3 4
# [2,] 5 6 7 8
パイプ演算子%>% {...}
中括弧は、パイプが関数内の最初の引数を使用しないようにする。
03. 円周率
str <- "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
str %>% str_replace_all(",|\\.", "") %>% strsplit(" ") %>% unlist() %>% nchar()
# -> [1] 3 1 4 1 5 9 2 6 5 3 5 8 9 7 9
str_replace_all(string, pattern, replacement)
stringrパッケージ
04. 元素記号
マッピング関数を使うと自然に書ける。
str <- "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
index <- c(1,5,6,7,8,9,15,16,19)
str %>% strsplit(" ") %>% unlist() -> strs
strs <- 1:length(strs) %>% map(function(i) {
setNames(i, substring(strs[i], 1, ifelse(i %in% index, 1, 2)))
}) %>% unlist()
strs
# -> H He Li Be B C N O F Ne Na Mi Al Si P S Cl Ar K Ca
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
マッピング関数を使わないと、要素を1つ1つ上書きして、最後にそれを名前として数字につけるようなやり方でできる。
for (i in 1:length(strs)) {
strs[i] %>% substring(1, ifelse(i %in% index, 1, 2)) -> strs[i]
}
1:length(strs) %>% setNames(strs) -> strs
for (i in 1:length(strs)) { ... }
の3行は
strs[index] %>% substring(1, 1) -> strs[index]
strs[-index] %>% substring(1, 2) -> strs[-index]
と同じ。これはこれでシンプル。
ifelse(test, yes, no)
三項演算子test ? yes : no
は無い。
map(.x, .f, ...)
purrrパッケージ(標準でMap関数があるが、引数の順番が扱いにくい)
マッピング(写像)。ベクトルの各要素に処理f(関数)を施す。リストを返す。これは1つの要素からベクトルを返すようなことができるため。
簡単な例:ベクトル1,2,3,4,5を与え、各要素を2倍にする。(1:5 * 2
をmapを使って表すと)
1:5 %>% map(function(i) i * 2) %>% unlist()
# -> [1] 2 4 6 8 10
# 省略記法
1:5 %>% map(~ . * 2) %>% unlist()
上のようにfunction(i) {i * 2}
とせずに1行なら{}
を省略できる。
05. n-gram
# ベクトルにしてから渡す
n_gram <- function(input_seq, n = 2) {
if (n == 1) return(input_seq)
embed(input_seq, n)[, n:1] %>% asplit(1)
}
n_gram("I am an NLPer" %>% strsplit(" ") %>% unlist())
# -> [[1]] [1] "I" "am"
# [[2]] [1] "am" "an"
# [[3]] [1] "an" "NLPer"
n_gram("I am an NLPer" %>% strsplit("") %>% unlist())
# -> [[1]] [1] "I" " "
# [[2]] [1] " " "a"
# [[3]] [1] "a" "m"
# [[4]] [1] "m" " "
# [[5]] [1] " " "a"
# [[6]] [1] "a" "n"
# [[7]] [1] "n" " "
# [[8]] [1] " " "N"
# [[9]] [1] "N" "L"
# [[10]] [1] "L" "P"
# [[11]] [1] "P" "e"
# [[12]] [1] "e" "r"
n=1のときasplit関数でエラーになるので、そのまま返している。
embed(x, dimension = 1)
まさにn-gram。行列で返す。列は欲しい順番と逆順になっているので、[, n:1]
で交換。
asplit(x, MARGIN)
行列をリストにする。MARGINが1で行ごと、2で列ごと。
06. 集合
bi_gram_x <- n_gram("paraparaparadise" %>% strsplit("") %>% unlist()) %>%
unlist() %>% matrix(nrow = 2) %>% apply(2, paste, collapse = "")
bi_gram_y <- n_gram("paragraph" %>% strsplit("") %>% unlist()) %>%
unlist() %>% matrix(nrow = 2) %>% apply(2, paste, collapse = "")
# 和集合
union(bi_gram_x, bi_gram_y)
# -> [1] "pa" "ar" "ra" "ap" "ad" "di" "is" "se" "ag" "gr" "ph"
# 積集合
intersect(bi_gram_x, bi_gram_y)
# -> [1] "pa" "ar" "ra" "ap"
# 差集合
setdiff(bi_gram_x, bi_gram_y)
# -> [1] "ad" "di" "is" "se"
"se" %in% bi_gram_x
# -> [1] TRUE
"se" %in% bi_gram_y
# -> [1] FALSE
%>% unlist() %>% matrix(nrow = 2) %>% apply(2, paste, collapse = "")
はn_gram関数から受け取ったリストを文字列のベクトルに変換している。(この場合%>% unlist() %>% matrix(nrow = 2) %>% apply(2, paste, collapse = "")
の代わりにlapply関数が使えた。lapply関数は問16を参照。)
n_gramのnを変えた場合は、matrix関数に渡している引数nrowをそのnに変える。
apply(X, MARGIN, FUN, ...)
行列Xを列ごとか行ごと(MARGINで指定)にFUNで変換し、ベクトルで返す。...
はFUNの引数。
例題
文字列"xxyyxyxy"を2文字ずつ"xx" "yy" "xy" "xy"に分けたい。
"xxyyxyxy" %>% strsplit("") %>% unlist() %>% matrix(nrow = 2) %>% apply(2, paste, collapse = "")
# -> [1] "xx" "yy" "xy" "xy"
文字列 → 行列 → ベクトル。分かりやすい方法でいいと思います。
別の例はstack overflowで。
07. テンプレートによる文生成
make_time_message <- function(x, y, z) {
sprintf("%s時の%sは%s", x, y, z)
}
make_time_message(12, "気温", 22.4)
# -> [1] "12時の気温は22.4"
paste(x, "時の", y, "は", z, sep = "")
でもOK。
08. 暗号文
cipher <- function(target_str) {
target_str %>% strsplit("") %>% unlist() %>% map(function(s) {
raw_s <- as.integer(charToRaw(s))
ifelse(as.integer(charToRaw("a")) <= raw_s && raw_s <= as.integer(charToRaw("z")), rawToChar(as.raw(219 - raw_s)), s)
}) %>% unlist() %>% paste(collapse = "")
}
mapを使わないバージョン。
cipher <- function(target_str) {
target_str %>% strsplit("") %>% unlist() -> target_str_s
for (i in 1:length(target_str_s)) {
raw_i <- as.integer(charToRaw(target_str_s[i]))
if (as.integer(charToRaw("a")) <= raw_i && raw_i <= as.integer(charToRaw("z"))) {
rawToChar(as.raw(219 - raw_i)) -> target_str_s[i]
}
}
return(paste(target_str_s, collapse = ""))
}
cipher("Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.")
# -> [1] "Nld I mvvw z wirmp, zoxlslorx lu xlfihv, zugvi gsv svzeb ovxgfivh rmeloermt jfzmgfn nvxszmrxh."
cipher("Nld I mvvw z wirmp, zoxlslorx lu xlfihv, zugvi gsv svzeb ovxgfivh rmeloermt jfzmgfn nvxszmrxh.")
# -> [1] "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
cipher("abcdefghijklmnopqrstuvwxyz")
# -> [1] "zyxwvutsrqponmlkjihgfedcba"
1文字ずつ小文字かどうかを調べて、小文字だったら文字コードを219 - 文字コード
に変更する。
09. Typoglycemia
map関数を使うバージョン。
typoglycemia <- function(target_str) {
target_str %>% strsplit(" ") %>% unlist() %>% map(function(s) {
ifelse(nchar(s) > 4, {
s %>% strsplit("") %>% unlist() -> ss
len_ss <- length(ss)
c(ss[1], sample(ss[2:(len_ss-1)], size = len_ss-2), ss[len_ss]) %>% paste(collapse = "")
}, s)
}) %>% unlist() %>% paste(collapse = " ")
}
使わないバージョン。
typoglycemia <- function(target_str) {
target_str %>% strsplit(" ") %>% unlist() -> target_str_s
for (i in 1:length(target_str_s)) {
if (nchar(target_str_s[i]) > 4) {
target_str_s[i] %>% strsplit("") %>% unlist() -> ss
len_ss <- length(ss)
c(ss[1], sample(ss[2:(len_ss-1)], size = len_ss-2), ss[len_ss]) %>%
paste(collapse = "") -> target_str_s[i]
}
}
return(paste(target_str_s, collapse = " "))
}
typoglycemia("I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")
# -> [1] "I cud'nolt bleeive that I could altclauy unanretdsd what I was riendag : the panenoemhl pweor of the human mind ."
ss[2:(len_ss-1)]
のところ括弧なくて一瞬ハマった。
1:5 - 1
はc(0,1,2,3,4)
か、確かにマイナスの前後開けたら:
の方が順位高く見える。
1単語ずつ5文字以上か調べ、5文字以上だったら中の文字をランダムに並び替え。
sample(x, size, replace = FALSE)
ベクトルxからsize個ランダムに抽出する。replace = TRUE
で重複を許す。
最適すぎる関数。
第2章 UNIXコマンド
とにかく遅い(それでも、コマンドが40msなら、Rscriptは400〜600ms程度)けど問題じゃない。
各問、一番最後の$
から始まるのがUNIXコマンドです。
ファイルを読み込む
1行ずつベクトルに入れる。
lines <- readLines("popular-names.txt")
テーブルに入れる。
data <- read.table("popular-names.txt")
names(data) <- c("name", "sex", "num", "year")
head(data, 3)
# -> name sex num year
# 1 Mary F 7065 1880
# 2 Anna F 2604 1880
# 3 Emma F 2003 1880
以下では、このlinesかdataが使われています。
10. 行数のカウント
length(lines)
# -> [1] 2780
$ wc popular-names.txt
2780 11120 55026 popular-names.txt
# 行数 単語数 文字数
11. タブをスペースに置換
linesr <- lines %>% str_replace_all("\t", " ")
head(linesr, 3)
# -> [1] "Mary F 7065 1880" "Anna F 2604 1880" "Emma F 2003 1880"
$ cat popular-names.txt | tr "\t" " " | head -3
Mary F 7065 1880
Anna F 2604 1880
Emma F 2003 1880
12. 1列目をcol1.txtに,2列目をcol2.txtに保存
data$name %>% as.vector() %>% paste(collapse = "\n") %>% write("col1.txt")
data$sex %>% as.vector() %>% paste(collapse = "\n") %>% write("col2.txt")
data$name
のようにして得られるfactor型はlevels(水準)を含んでいるので全て取り除くためにas.vector()
を通している。(正しい方法か分かりません。droplevels関数は使用してないlevelsを取り除く。)
levelsは問17のような場面で有効です。
$ cut -f 1 popular-names.txt > col1.txt
$ cut -f 2 popular-names.txt > col2.txt
diffコマンドで差分がないことが確認できます。
13. col1.txtとcol2.txtをマージ
col1 <- readLines("col1.txt")
col2 <- readLines("col2.txt")
paste(col1, col2, sep = "\t") %>% head(3)
# -> [1] "Mary\tF" "Anna\tF" "Emma\tF"
paste(col1, col2, sep = "\t") %>% head(3) %>% cat(sep = "\n")
# -> Mary F
# Anna F
# Emma F
paste(..., sep = " ", collapse = NULL)
paste(c(1,2,3,4), c(5,6,7,8), sep = " ")
# -> [1] "1 5" "2 6" "3 7" "4 8"
ベクトルを2つ以上渡すとこんな挙動。
cat(..., file = "", sep = " ")
ベクトルを文字列として出力する。sepで与えた文字列を挟む。
$ paste -d "\t" col1.txt col2.txt | head -3
Mary F
Anna F
Emma F
14. 先頭からN行を出力
head(lines, 3)
# -> [1] "Mary\tF\t7065\t1880" "Anna\tF\t2604\t1880" "Emma\tF\t2003\t1880"
$ head -3 popular-names.txt
Mary F 7065 1880
Anna F 2604 1880
Emma F 2003 1880
15. 末尾のN行を出力
tail(lines, 3)
# -> [1] "Lucas\tM\t12585\t2018" "Mason\tM\t12435\t2018" "Logan\tM\t12352\t2018"
$ tail -3 popular-names.txt
Lucas M 12585 2018
Mason M 12435 2018
Logan M 12352 2018
16. ファイルをN分割する
split_lines <- function(lines, n) {
split(lines, rep_len(1:n, length(lines)) %>% sort()) %>% setNames(NULL)
}
split_lines(lines, 3) %>% lapply(head, 3)
# -> [[1]] [1] "Mary\tF\t7065\t1880" "Anna\tF\t2604\t1880" "Emma\tF\t2003\t1880"
# [[2]] [1] "Virginia\tF\t16162\t1926" "Mildred\tF\t13551\t1926" "Frances\tF\t13355\t1926"
# [[3]] [1] "John\tM\t43181\t1972" "Robert\tM\t43037\t1972" "Jason\tM\t37446\t1972"
rep_len(x, length.out)
ベクトルxを長さlength.outになるまで繰り返したベクトルを返す。rep(x, length = length.out)
と同じ。
sort(x, decreasing = FALSE)
ベクトルxをソートして返す。デフォルトでは昇順、decreasingをTRUEにすると降順。
rep_len(1:3, 10)
# -> [1] 1 2 3 1 2 3 1 2 3 1
rep_len(1:3, 10) %>% sort()
# -> [1] 1 1 1 1 2 2 2 3 3 3
split(x, f, drop = FALSE)
ベクトルxをベクトルfに対応してリストに分類する。
fにlevelsを持つfactorを渡したときに、未使用のlevelsを除外するかしないかがdrop。
(split(c(1,2,3,4,5,6), names %>% head(2), drop = T)
のdropあるなし、試してみて!)
split(c(1,2,3,4,5), c(1,1,1,2,2))
# -> $`1` [1] 1 2 3
# $`2` [1] 4 5
split(c(1,2,3,4,5,6), c("a", "b"))
# -> $a [1] 1 3 5
# $b [1] 2 4 6
setNames(object = nm, nm)
splitのfに数字のベクトルを渡しても、リストの名前はその数字の文字列になってしまいます。(これは、与えられた数字のベクトルが1からの連番になってるとは限らないからだと思います。)namesにNULLを設定すると、リストのインデックスは1からの数字の連番になります。(必要なければやらなくていい。)
lapply(X, FUN, ...)
リストXに対してインデックスごとにFUNに渡す。FUNの引数はそれ以降に書く。
$ split -l 1000 popular-names.txt popular-names-
1000行ずつ、popular-names-aa、popular-names-ab、popular-names-acの3つのファイルができる。
macのsplitコマンドはn分割できない。
17. 1列目の文字列の異なり
data$name %>% levels() %>% head(6)
# -> [1] "Abigail" "Aiden" "Alexander" "Alexis" "Alice" "Amanda"
貯金箱にいっぱい硬貨が入っているけど、あるのは、1円、5円、10円、50円、100円、500円だけ。
levels(x)
factor xのlevelsをベクトルで取得する。
$ cut -f 1 popular-names.txt | sort | uniq | head -6
Abigail
Aiden
Alexander
Alexis
Alice
Amanda
uniqコマンドは連続する2行が同じなら1行消去する。
18. 各行を3コラム目の数値の降順にソート
data[order(data$num, decreasing = T),]
# -> name sex num year
# 1341 Linda F 99689 1947
# 1361 Linda F 96211 1948
# 1351 James M 94757 1947
# 1551 Michael M 92704 1957
# ...
order(..., na.last = TRUE, decreasing = FALSE)
ソートしたときの順序をベクトルで返す。デフォルトでは昇順、decreasingをTRUEにすると降順。
x <- c(4,6,8,2,7,1,5,9,3)
sort(x)
# -> [1] 1 2 3 4 5 6 7 8 9
order(x)
# -> [1] 6 4 9 1 7 2 5 3 8
# 6番、4番、9番、1番、 ··· の順に並べたらソートされる
x[order(x)]
# -> [1] 1 2 3 4 5 6 7 8 9
$ sort -n -r -k 3 popular-names.txt | head -4
Linda F 99689 1947
Linda F 96211 1948
James M 94757 1947
Michael M 92704 1957
sortコマンドのオプション
-
-n
数値としてソート -
-r
降順でソート -
-k 3
ソートに使う列のキー
19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる
data$name %>% table() %>% sort(decreasing = T) %>% names() %>% head(6)
# -> [1] "James" "William" "John" "Robert" "Mary" "Charles"
table(...)
table関数にベクトルやfactorを渡すとそれぞれの要素と要素数が結びついたテーブルが得られる。
$ cut -f 1 popular-names.txt | sort | uniq -c | sort -n -r -k 1 | head -6
118 James
111 William
108 Robert
108 John
92 Mary
75 Charles
uniqコマンドのオプション
-
-c
連続する同じ行の行数を数え、表にする。列1に要素数、列2に要素。
第3章 正規表現
20. JSONデータの読み込み
1行1記事だから{"title": "イギリス"
から始まる行を取れば良い。
data <- readLines("jawiki-country.json")
article_UK <- grep("^\\{\"title\": \"イギリス\"", data, value = T)
lines_UK <- article_UK %>% strsplit("\\n", fixed = T) %>% unlist()
grep(pattern, x, value = FALSE)
ベクトルxのうち、正規表現patternのマッチするもののインデックスを得る。value = TRUE
にすると要素のベクトルを得る。
strsplit(x, split, fixed = FALSE)
fixed = TRUE
としないと、エスケープされた改行に一致してくれない。
ちなみに、jawiki-country.jsonは次のような感じになっています。
length(data)
# -> [1] 248
data %>% head(5) %>% substring(1, 20)
# -> [1] "{\"title\": \"エジプト\", \"t" "{\"title\": \"オーストリア\", " "{\"title\": \"インドネシア\", "
# [4] "{\"title\": \"イラク\", \"te" "{\"title\": \"イラン\", \"te"
lines_UK %>% head(10) %>% cat(sep = "\n")
# -> {"title": "イギリス", "text": "{{redirect|UK}}
# {{redirect|英国|春秋時代の諸侯国|英 (春秋)}}
# {{Otheruses|ヨーロッパの国|長崎県・熊本県の郷土料理|いぎりす}}
# {{基礎情報 国
# |略名 =イギリス
# |日本語国名 = グレートブリテン及び北アイルランド連合王国
# |公式国名 = {{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br />
# *{{lang|gd|An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn mu Thuath}}([[スコットランド・ゲール語]])
# *{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon}}([[ウェールズ語]])
# *{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart na hÉireann}}([[アイルランド語]])
21. カテゴリ名を含む行を抽出
lines_UKから[[Category:ほげほげ]]
を含む行を取り出す。(行で取り出すと最後のにおまけがついてくるから、ここでは行で取り出していない。含む行を取り出すためにはgrep("\\[\\[Category:.*\\]\\]", lines_UK, value = T)
とする必要がある。)
lines_UK %>% str_extract_all("\\[\\[Category:.*\\]\\]") %>% unlist() -> categories_UK
cat(categories_UK, sep = "\n")
# -> [[Category:イギリス|*]]
# [[Category:イギリス連邦加盟国]]
# [[Category:英連邦王国|*]]
# [[Category:G8加盟国]]
# [[Category:欧州連合加盟国|元]]
# [[Category:海洋国家]]
# [[Category:現存する君主国]]
# [[Category:島国]]
# [[Category:1801年に成立した国家・領域]]
str_extract_all(string, pattern)
stringrパッケージ
正規表現patternの部分を満たして、文字列のリストを返す。マッチした行(要素)が必要じゃなければいきなりstr_extract_all関数を使うべき。
22. カテゴリ名の抽出
categories_UK %>% str_replace_all("^\\[\\[Category:|\\]\\]$", "") %>% cat(sep = "\n")
# -> イギリス|*
# イギリス連邦加盟国
# 英連邦王国|*
# G8加盟国
# 欧州連合加盟国|元
# 海洋国家
# 現存する君主国
# 島国
# 1801年に成立した国家・領域
23. セクション構造
sections_UK <- str_extract_all(lines_UK, "==.*==") %>% unlist()
sections_UK <- matrix(c(
sections_UK %>% str_extract_all("^=*") %>% unlist() %>% nchar() - 1,
sections_UK %>% str_replace_all("\\s*={2,4}\\s*", "")
), ncol = 2)
sections_UK %>% apply(1, paste, collapse = " ") %>% head(10) %>% cat(sep = "\n")
# -> 1 国名
# 1 歴史
# 1 地理
# 2 主要都市
# 2 気候
# 1 政治
# 2 元首
# 2 法
# 2 内政
# 2 地方行政区分
24. ファイル参照の抽出
str_extract_all(lines_UK, "\\[\\[ファイル:.*?(\\||\\]\\])") %>% unlist() %>%
str_replace_all("^\\[\\[ファイル:|\\|$|\\]\\]$", "") %>% head(10) %>% cat(sep = "\n")
# -> Royal Coat of Arms of the United Kingdom.svg
# United States Navy Band - God Save the Queen.ogg
# Descriptio Prime Tabulae Europae.jpg
# Lenepveu, Jeanne d'Arc au siège d'Orléans.jpg
# London.bankofengland.arp.jpg
# Battle of Waterloo 1815.PNG
# Uk topo en.jpg
# BenNevis2005.jpg
# Population density UK 2011 census.png
# 2019 Greenwich Peninsula & Canary Wharf.jpg
25. テンプレートの抽出
article_UK %>% str_extract_all("\\{\\{基礎情報.*?(\\{\\{.*?\\}\\}.*?)*?\\}\\}") %>% unlist() %>%
strsplit("\\n", fixed = T) %>% unlist() %>% grep("^\\|", ., value = T) -> std_info_lines
std_info <- std_info_lines %>% str_extract_all("=\\s*.*?$") %>% unlist() %>% str_replace_all("^=\\s*", "")
names(std_info) <- std_info_lines %>% str_extract_all("^\\|.*?\\s*=") %>% unlist() %>% str_replace_all("^\\||\\s*=$", "")
matrix(c(names(std_info), std_info), ncol = 2) %>% apply(1, paste, collapse = ": ") %>% head(10) %>% cat(sep = "\n")
# -> 略名: イギリス
# 日本語国名: グレートブリテン及び北アイルランド連合王国
# 公式国名: {{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br />
# 国旗画像: Flag of the United Kingdom.svg
# 国章画像: [[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]
# 国章リンク: ([[イギリスの国章|国章]])
# 標語: {{lang|fr|[[Dieu et mon droit]]}}<br />([[フランス語]]:[[Dieu et mon droit|神と我が権利]])
# 国歌: [[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}<br />''神よ女王を護り賜え''<br />{{center|[[ファイル:United States Navy Band - God Save the Queen.ogg]]}}
# 地図画像: Europe-UK.svg
# 位置画像: United Kingdom (+overseas territories) in the World (+Antarctica claims).svg
26. 強調マークアップの除去
''
と'''
を取り除く。(でいいの?)
std_info_lines <- std_info_lines %>% str_replace_all("''|'''", "")
std_info <- std_info_lines %>% str_extract_all("=\\s*.*?$") %>% unlist() %>% str_replace_all("^=\\s*", "")
names(std_info) <- std_info_lines %>% str_extract_all("^\\|.*?\\s*=") %>% unlist() %>% str_replace_all("^\\||\\s*=$", "")
27. 内部リンクの除去
お待ちください。
28. MediaWikiマークアップの除去
今後追加します。
29. 国旗画像のURLを取得する
image_info <- read_json("https://en.wikipedia.org/w/api.php?action=query&format=json&prop=imageinfo&iiprop=url&titles=File:Flag%20of%20the%20United%20Kingdom.svg")
image_info$query$pages$`23473560`$imageinfo[[1]]$url
# -> [1] "https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg"
jsonliteパッケージを使用。
第4章 形態素解析
形態素解析
neko.txtは1文1行で記載されていて、mecabは1行ずつ解析する。
$ cat neko.txt | mecab -o neko.txt.mecab_ \
# --bos-format='surface\tbase\tpos\tpos1\n' \
--node-format='%m\t%f[6]\t%f[0]\t%f[1]\n' \
--unk-format='%m\t%m\t\t\n' \
--eon-format='EOS\n'
mecabコマンドのオプション
-
-o
出力ファイル名 - その他 出力フォーマットオプション
このままだと、後でRでテーブルとして読み込むときに'
が邪魔をするのでエスケープします。(上のようにフォーマットを設定するとエスケープしなくても読み込めますが、文中の'
が無かったことになる。)詳細はあまり関係ないけど今度追記。
$ sed -e "s/'/\\\'/g" neko.txt.mecab_ > neko.txt.mecab
neko.txt.mecabは
surface base pos pos1 <- ヘッダー(上でコメントアウトしているからない)
......
を を 助詞 格助詞
見る 見る 動詞 自立
......
EOS <- End Of Sentence
surface base pos pos1 <- ヘッダー(上でコメントアウトしているからない)
......
EOS <- End Of Sentence
......
のように1文ずつ形態素解析されて分かれている。文と文はEOSで区切られている。(ヘッダーはコメントアウトした。)
30. 形態素解析結果の読み込み
neko.dt <- fread("neko.txt.mecab", fill = T, col.names = c("surface", "base", "pos", "pos1"))
1文ずつテーブルを分けるのは処理に時間がかかるので、全ての文を同じテーブルに入れた。同じテーブルに入れても、文と文の間に読点かEOSがあれば問題ない気がする。(書き直すかも。)
テーブルフレームなど、調べたら書き直す。現段階では次で結果が得られる。
31. 動詞
neko.dt[pos=="動詞"]$surface %>% unique() %>% sort()
32. 動詞の原形
neko.dt[pos=="動詞"]$base %>% unique() %>% sort()
33. 「AのB」
neko.pos3 <- neko.dt$pos %>% embed(3) %>% .[, 3:1]
neko.surface3 <- neko.dt$surface %>% embed(3) %>% .[, 3:1]
AnoB.index <- grep("の", neko.surface3[,2]) %>%
intersect(grep("名詞", neko.pos3[,1])) %>%
intersect(grep("助詞", neko.pos3[,2])) %>%
intersect(grep("名詞", neko.pos3[,3]))
neko.surface3[AnoB.index,] %>% apply(1, paste, collapse = "") # 出現順