前回までの進捗
こんにちは、小幡です。Rを学んでおります。
前回までで「dplyr」というパッケージを使ってデータフレームの操作の仕方を一通り学び終えました。フィルターしたり、並び替えたり、要約したり、といった7つの基本的な関数を組み合わせることで、かなり自由にデータフレームを操作できるようになったと思います。inner_joinやleft_joinで複数のデータフレームを結合した操作もできるようになりました。
そして、第7回と第8回では、「dplyr」の7つの関数と組み合わせることで、複雑な条件でのフィルターや要約などの操作を行う方法を学ぶそうです。
学んできたこととこれから学ぶことの関係を知ろう
輿石さん:さて、第4回では以下の順番でデータフレームの操作に習熟していこうと話をしました。覚えていますか?
小幡:①の7つの基本操作がdplyrで学んだ7つの関数ですね。①を終えて、次は②ということですね!
輿石さん:その通りです!もう少し①と②の関係を説明してみます。dplyrは7つの基本操作を組み合わせて複雑な処理を実現していましたね。その流れは以下のようになっています。
new_data <- data %>% filter(列の計算や指定) %>% select(列の計算や指定) %>% group_by(列の計算や指定) %>% mutate(列の計算や指定) %>% summarise(列の計算や指定)
輿石さん: filterやmutateなどの7つの基本操作の中で、「どんな条件で基本操作を行うか?」という細かい条件を記述していますね。この条件の指定のバリエーションを増やすことでもっともっとできることが広がっていきます。
株式会社ヴァリューズ ソリューション局 マネジャー
輿石拓真(こしいし・たくま)さん
※ヴァリューズオフィスの11F「Peak Bar」にて(2020年2月に撮影)
小幡さん: なるほど。バリエーションってどんなものがあるんでしょうか?
輿石さん: 今までは、足し算などの四則演算や、「=」「>=」での論理判定など基本的なものを扱ってきました。具体例を示しますね。今回もサプリメントに関するサンプルデータを使っていきます。次のコードをテキストエディタにコピペして実行してみましょう。
# packageの読み込み
library(tidyverse)
# データの読み込み
kenko_ec_data <- read_tsv("https://adcreative.valuesccg.net/manamina/article/manaminar/kenko_ec_sample.tsv")
kenko_ec_data
●コンソールの出力内容
#> # A tibble: 10,361 x 7
#> userid purchase_date item_category series price purchase_amount
#> <chr> <date> <chr> <chr> <dbl> <int>
#> 1 K00002 2020-01-07 マルチ酵素 お徳用 3186 1
#> 2 K00003 2020-03-24 カルシウム プレミアム~ 934 1
#> 3 K00005 2020-01-30 クレアチン スタンダー~ 2560 1
#> 4 K00008 2020-01-30 乳酸菌 プレミアム~ 2574 1
#> 5 K00009 2020-01-15 クエン酸 スタンダー~ 1080 1
#> 6 K00010 2020-03-22 ビタミンB お徳用 1617 1
#> 7 K00019 2020-03-21 グルコサミン お徳用 5329 3
#> 8 K00023 2020-01-05 ビタミンB プレミアム~ 778 1
#> 9 K00027 2020-03-13 スピルリナ お徳用 5713 5
#> 10 K00031 2020-02-12 BCAA スタンダー~ 3694 1
#> # ... with 10,351 more rows, and 1 more variable: item_name <chr>
kenko_ec_data %>% # 論理演算子「>」「&」「==」を使って、複数個同時購入されたカルシウム製品にデータをフィルターする。 filter(purchase_amount > 2 & item_category == "カルシウム") %>% # 掛け算を行う「*」を使って、支払金額を計算した列を追加する。 mutate(payment = price * purchase_amount) %>% # 確認のためにselectで列を絞って見やすく表示します。 select(userid, purchase_date, item_category, price, purchase_amount, payment)●コンソールの出力内容
#> # A tibble: 85 x 6
#> userid purchase_date item_category price purchase_amount payment
#> <chr> <date> <chr> <dbl> <dbl> <dbl>
#> 1 K00129 2020-03-30 カルシウム 719 4 2876
#> 2 K00129 2020-03-30 カルシウム 1941 3 5823
#> 3 K00033 2020-01-31 カルシウム 719 3 2157
#> 4 K00811 2020-03-17 カルシウム 719 4 2876
#> 5 K00174 2020-03-17 カルシウム 934 3 2802
#> 6 K00924 2020-03-12 カルシウム 1941 5 9705
#> 7 K01352 2020-02-13 カルシウム 934 4 3736
#> 8 K01167 2020-01-25 カルシウム 934 5 4670
#> 9 K02011 2020-02-26 カルシウム 934 3 2802
#> 10 K02560 2020-01-07 カルシウム 719 4 2876
#> # ... with 75 more rows
輿石さん:このように、基本的な四則演算と論理演算子でも色々とできましたよね。これ以上のバリエーションというと数えきれないほどあるのですが、今回はトピックとしては2つ、「文字列」と「日時」の操作にについて学んでいきます。マーケティング関連のデータを扱う業務を行う上で遭遇する頻度が高く、一つ学ぶと応用が利いて、できることの幅が広がるトピックだと思います。
また、「実は数えきれないほどある」というとなんだか学習を終えるには程遠い感じを受けますが、今回扱う範囲だけでデータの操作については大半のニーズを満たせるんじゃないかと思っています。数えきれないほどあるものを全部知っている人もいないので安心してくださいね。
「ベクトル」に入門しよう
輿石さん:dplyrでは「データフレームをインプットにデータフレームをアウトプットする関数」を学んできましたね。今回扱うdplyrの内部で使う関数は何をインプットにしているのでしょうか。
第2回の復習ですが、関数を使う上でインプットとアウトプットが何かを意識することはとても大事です。では、以下のコード内の関数sumのインプットは何でしょう?
kenko_ec_data %>% summarise(amount_sum = sum(purchase_amount))●コンソールの出力内容
#> # A tibble: 1 x 1
#> amount_sum
#> <dbl>
#> 1 17398
小幡:purchase_amountという列ですよね。
輿石さん:そうですね。さらに言うと、purchase_amountの中身は「1, 1, 1, 1, 1, 1, 3, 1, 5,...」という数値の集まりですよね。sumはこの数値の集まりをインプットにしています。Rではこのような同じ性質をもった値の集まりを「ベクトル」と言います。データフレームの各列もベクトルなんです。
小幡:「同じ性質をもった」ってどういうことですか?
輿石さん:良い質問ですね! 例えば、purchase_amount列は購入個数なので必ず数値が入っていますよね?300行目に突然「"こんにちは!"」とか文字が入っていることはありません。purchase_date列だったら必ず日付が入っているはずです。この同じ性質をRでは型と呼んでいて、「数値型(1や2)」「文字列型("田中")」などがあります。
小幡:「同じ性質をもった」というのは難しいことではなく、当たり前なことな気がしてきましたね。
輿石さん:データの集まりを扱う上で自然ですよね。以下に代表的な型をまとめました。
型の名前(日本語) | 型の名前(英語) | 例 |
整数値型 | integer | 1, 2, 3, 4, 5, 6, ... |
実数値型 | double | 1.2, 1.3, 1.0, 2.3, ... |
数値型 | numeric | 整数値と実数値を区別せずにnumericと表す |
文字列型 | character | "カルシウム", "ビタミン", "クエン酸", ... ※クオートで囲まれているのが特徴。 |
真偽値型 | logical | TRUE, FALSE, TURE, FALSE, FALSE, ... |
日付型 | date | 2020-10-30, 2020-11-01, ... |
日時型 | dttm, POSIXct, POSIXt | 2020-10-30 10:10:10, ... |
輿石さん:型の大事さを理解してもらうためにもう一つ、sum()という関数を考えてみましょう。sumは足し算する関数なので、インプットは数値である必要があります。"カルシウム"と"ビタミン"という文字列をsumすることはできませんよね。ここから関数のインプットが何であるかが大事という一面が感じられるんじゃないかなと思っています。
データフレームの各列の型を調べる方法は色々ありますが、簡単な方法はコンソールで表示してみることです。列名の下のカッコ内に型が書かれています。Rstudioのenviromentalパネルでも確認できますよ。
stringrパッケージに詳しくなろう
輿石さん:では本題に入っていきましょう。今日のトピックは文字列の操作です。Rっぽくいうと文字列型ベクトルの操作がテーマです。文字列の操作は「stringr」というパッケージに含まれる関数を使います。
小幡:文字列の操作というのは、具体的にどんなことをするのでしょうか?
輿石さん:色々あるのですが、今回は「文字列の連結」「文字列が含まれているかの判定」「文字列の置換」の3つの操作を紹介します。ただ、実際にはもっとたくさんのことが文字列の操作にはありますし、この3つで足りないこともでてくると思います。でも「文字列操作はstringrだ!」ということを知っていれば、今日学ぶことの応用でなんとかなるはずです。まずはstringrパッケージの説明をしたいと思います。
stringrパッケージはdplyrパッケージと同様にtidyverseパッケージに含まれているので、library(tidyverse)を実行することで使えるようになります。まずはstringrパッケージに含まれる関数の一覧を見てみましょう。
ls("package:stringr")●コンソールの出力内容
#> [1] "%>%" "boundary" "coll" "fixed" "fruit"
#> [6] "invert_match" "regex" "sentences" "str_c" "str_conv"
#> [11] "str_count" "str_detect" "str_dup" "str_ends" "str_extract"
#> [16] "str_extract_all" "str_flatten" "str_glue" "str_glue_data" "str_interp"
#> [21] "str_length" "str_locate" "str_locate_all" "str_match" "str_match_all"
#> [26] "str_order" "str_pad" "str_remove" "str_remove_all" "str_replace"
#> [31] "str_replace_all" "str_replace_na" "str_sort" "str_split" "str_split_fixed"
#> [36] "str_squish" "str_starts" "str_sub" "str_sub<-" "str_subset"
#> [41] "str_to_lower" "str_to_sentence" "str_to_title" "str_to_upper" "str_trim"
#> [46] "str_trunc" "str_view" "str_view_all" "str_which" "str_wrap"
#> [51] "word" "words"
輿石さん:表示されている中で、str_から始まる関数が文字列の操作を行う関数です。たくさんの種類があって、いろいろなことがstringrでできそうだということが伝わるかなと思います。str_の後にはcountとかsortとか、removeとか、splitとかついていますね。それぞれどんな関数か想像つきますか?
小幡:countは文字列を数える、sortは並び替える、removeは取り除く、splitは分割する、、、なんとなくわかります!
輿石さん:見慣れない英単語もありますが、なんとなく関数の働きが想像つきますよね。こんな感じでやりたいことができそうな関数を探してもいいですし、逆に文字列をこう操作したい、というものがあれば「stringr」というキーワードと一緒にGoogle検索するといいでしょう。
小幡:なんだか緩いですね。
輿石さん:ちゃんと動作を確かめながら進めていけば大丈夫です!試行錯誤してどんな動きをするのか確かめていくのも上達方法の一つだと思いますよ。こちらの記事がそれぞれの関数の詳細を日本語で説明しているので目を通しておくと参考になります。
輿石さん:stringrパッケージのstr_から始まる関数にはもう一つ共通点があります。関数の第一引数に必ず操作をしたい文字列ベクトルを取ります。第二引数以降にどんな操作をしたいのかの詳細を指定する引数が用意されています。例えば、str_removeであればstr_remove(操作対象の文字列ベクトル, ”除きたい文字列”)という具合に記述します。どんな引数を持つかは`?関数名`と打つとRstudio上でヘルプが表示されますよ。
小幡:dplyrの7つの関数の第一引数が必ずデータフレームだったことと似ていますね。覚えやすい気がします!
1.文字列の連結
輿石さん:まず、文字列ベクトルの作り方を説明します。ベクトルはc()という関数で作ることができるんです。次の例のように、ダブルクオートやクオートで囲うと文字列ベクトルになります。
# c()でベクトルをつくる。 # ダブルクオートかクオートで囲むと文字列 number_chr_vec <- c('1', '2', '3') name_vec <- c("佐藤", "鈴木", "高橋") kensyo_vec <- c("さん", "君", "殿") sum(number_chr_vec)●コンソールの出力内容
#> sum(number_chr_vec) でエラー: 引数 'type' (character) が不正です
輿石さん:number_chr_vecの中身は見た目は数値ですが、クオートで囲まれているので文字列です。sum()は文字列を扱うことはできないのでしっかりエラーになっていますね。では、文字列ベクトルが作れたところで、str_c()という関数を使って文字列を結合してみましょう。
str_c(name_vec, kensyo_vec) str_c(name_vec, "さん") str_c(name_vec, c("さん", "君"))●コンソールの出力内容
# str_c(name_vec, kensyo_vec)
#>[1] "佐藤さん" "鈴木君" "高橋殿"
# str_c(name_vec, "さん")
#>[1] "佐藤さん" "鈴木さん" "高橋さん"
# str_c(name_vec, c("さん", "君"))
#>[1] "佐藤さん" "鈴木君" "高橋さん"
#> 警告メッセージ:
#> stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE) で:
#> longer object length is not a multiple of shorter object length
輿石さん:str_cはベクトルを複数渡すと、中身を一つずつくっつけてくれるんです。最初の例は名前3つと敬称3つをそれぞれくっつけていますね。シンプルで分かりやすいです。二つ目と三つめの例は、名前は3つに対して敬称は1つか2つで数が合っていないのですが、少ないほうが勝手に増えてうまいことくっついてます。Rではこんな感じで足りないものを増やして(再利用して)くれることが多々あります。この仕組みを利用することも多いので覚えておきましょう。
小幡:最後にエラーが出てませんか?
輿石さん:警告とあるので、エラーとは違うんです。処理自体は完了したんですけど、「3対2でくっつける状況は不自然じゃないですか?」と教えてくれてます。
小幡:なるほど。エラーとは違うんですね。安心しました。
輿石さん:それではstr_c()をdplyrの関数と組み合わせて使ってみましょう。ここでは、item_category列とseries列を結合したitem_categry_series列を追加してみます。
kenko_ec_data %>% mutate(item_category_series = str_c(item_category, "_", series)) %>% group_by(item_category_series) %>% summarise(purchase_amount_sum = sum(purchase_amount)) %>% arrange(desc(purchase_amount_sum))●コンソールの出力内容
#># A tibble: 154 x 2
#> item_category_series purchase_amount_sum
#> <chr> <dbl>
#> 1 ホエイプロテイン_プレミアム 754
#> 2 ホエイプロテイン_スタンダード 714
#> 3 ホエイプロテイン_お徳用 704
#> 4 亜鉛_スタンダード 549
#> 5 ビタミンB_お徳用 453
#> 6 食物繊維_プレミアム 439
#> 7 マルチビタミン&ミネラル_お徳用 394
#> 8 BCAA_スタンダード 374
#> 9 ビタミンC_プレミアム 374
#>10 クエン酸_プレミアム 333
#># ... with 144 more rows
小幡:mutateの中でこうやって使うんですね!でも、この場合は文字列を結合しなくてもitem_categoryとseriesの2列でgroup_byしたら同じ結果になりますよね?
輿石さん:鋭い!その通りなので今回はあまり有用な例でなくて申し訳ないです。でも、例えば今後Rでの可視化を学んだときや、もしくは少し高度な分析を行う関数を使いたい、となったときに、複数列の情報を1列にまとめる、というのは必要になると思うので覚えておきましょう!アウトプットの体裁を整える効果もありますね。
小幡:分かりました!
RStudioと向き合う小幡
ヴァリューズオフィスの11F「Peak Bar」にて(2020年2月に撮影)
2.文字列が含まれているか判定する
輿石さん:次に、str_detect()を使って、文字列が含まれているかどうか判定してみます。簡単な例として、名前の中に「高」という文字が含まれているかどうか判定してみたいと思います。
str_detect(name_vec, "高")●コンソールの出力内容
#> [1] FALSE FALSE TRUE
輿石さん:str_detect()は第一引数に判定対象の文字列ベクトルを、第二引数に判定したい文字列のパターンを渡します。「高」という文字が含まれているのは3つ目の「高橋」のみなので、アウトプットの3つめだけがTRUEになっていますね。str_detect()は真偽値の判定を行い、TRUEorFALSEの真偽値ベクトルを行う関数なんです。
小幡:真偽値というと論理演算子と一緒ですね。
輿石さん:そうです!またもや鋭い。一緒なので、「&」や「|」や「!」など論理演算子のところで学んだことはそのまま活用できますよ。この後のdplyrを使った例の中で組み合わせてみましょう。
輿石さん:まずは、今「ビタミン」に関して分析したいケースを考えましょう。ビタミンはビタミンCやAなど種類がたくさんありますよね。今このデータセットに何種類のビタミンに関するデータが含まれているか分かりますか?
小幡:うーん、ぱっとはわからないです。
輿石さん:そうですよね。str_detect()とdplyrの関数を組み合わせて調べてみます。「ビタミン」を含むitem_cetegoryの販売個数のランキングを集計してみましょう。
kenko_ec_data %>% filter(str_detect(item_category, pattern = "ビタミン")) %>% group_by(item_category) %>% summarise(purchase_amount = sum(purchase_amount)) %>% arrange(desc(purchase_amount))●コンソールの出力内容
#># A tibble: 5 x 2
#> item_category purchase_amount
#> <chr> <dbl>
#>1 ビタミンC 978
#>2 ビタミンB 952
#>3 マルチビタミン&ミネラル 738
#>4 ビタミンE 275
#>5 ビタミンD 233
小幡:filterの中でstr_detectを使うんですね!str_detect()は真偽値を返すので、filter(purchase_amount >= 2)としたときと同じ理屈で、TRUEの行だけに絞られるってことですよね。
輿石さん:その通りです!str_detect()は真偽値そのものなので、すでに学習した論理演算子を組み合わせることもできますよ。今話にでたpurchase_amountが2以上という複数個購入に絞って同じ計算をしてみましょう。
kenko_ec_data %>% filter(str_detect(item_category, pattern = "ビタミン") & purchase_amount >= 2) %>% group_by(item_category) %>% summarise(purchase_amount = sum(purchase_amount)) %>% arrange(desc(purchase_amount))●コンソールの出力内容
#># A tibble: 5 x 2
#> item_category purchase_amount
#> <chr> <dbl>
#>1 ビタミンC 476
#>2 ビタミンB 440
#>3 マルチビタミン&ミネラル 329
#>4 ビタミンD 112
#>5 ビタミンE 108
輿石さん:もう一つ、次はmutateと組み合わせてstr_detect()を使ってみましょう。ビタミン関連の商品は全体の売上のどれくらいの比率を占めているのでしょうか?ちょっと発展編で難しいかもしれませんが、頑張ってみましょう。
kenko_ec_data %>% mutate(payment = price * purchase_amount, vitamin_flg = str_detect(item_category, pattern = "ビタミン")) %>% group_by(vitamin_flg) %>% summarise(payment_sum = sum(payment))●コンソールの出力内容
#># A tibble: 2 x 2
#> vitamin_flg payment_sum
#> <lgl> <dbl>
#>1 FALSE 44119785
#>2 TRUE 5430329
輿石さん:ビタミンを含まない売上が4.4千万、ビタミン関連の売上が約5.5百万程度なようですね。こんな形で、str_detect()に限らずにTRUEorFALSEを返す関数や演算子すべてに言えることですが、mutateと組み合わせると当てはまるものと当てはまらないものに分けることができたりします。
小幡:str_detect()だけでもフィルターに使ったり、集計の軸として使ったり、いろんなことに使えるんですね。
3.文字列の置換と削除
輿石さん:3つ目に文字列の置換するstr_replace()とstr_replace_all()を紹介したいと思います。まずはstr_replace()から見てみます。
greeting_vec <- c("おはよう", "こんにちは", "こんばんは") str_replace(greeting_vec, pattern = "ん", replacement = "@")●コンソールの出力内容
#> [1] "おはよう" "こ@にちは" "こ@ばんは"
輿石さん:第二引数にどんな文字列を置換したいか、第三引数に何に置換したいかを指定します。今回は「ん」という文字を「@」という文字に置換するコードです。アウトプットをよーくみると、「こんばんは」の二つ目の「ん」が変わっていませんね。str_replace()は最初にマッチしたところしか置換しない関数なんです。すべてを置換したいときにはstr_replace_all()という「_all」が付いた関数を使います。
greeting_vec <- c("おはよう", "こんにちは", "こんばんは") str_replace_all(greeting_vec, pattern = "ん", replacement = "@")●コンソールの出力内容
#> [1] "おはよう" "こ@にちは" "こ@ば
@は"
輿石さん:細かな違いですが、結果が全く異なってしまう大事な違いなので頭の片隅に入れておいてくださいね。
正規表現を学んで文字列操作の高みを目指そう
輿石さん:ここまではstringrパッケージの中でも頻繁に使うと個人的に思った3つの関数について紹介しました。この3つ以外にも関数がたくさんあるので、他の関数も見てみてくださいね。関数の知識をつける以外に、文字列操作に習熟する上で大事な知識がもう一つあります。正規表現です。stringrのpattern引数にはこの正規表現を記載することができるんです。
小幡:正規表現…?ちょっと聞いたことないですね。
輿石さん:正規表現は文字列のパターンを柔軟に指定するための表現方法です。いくつかの文字列を一つの形式で表現するための表現方法ともいえます。
小幡:う~ん、よくわからないですね。
輿石さん:そうですよね。正規表現は最初はとっつきにくいですが、一回覚えたら一生使える知識になります。正規表現はRだけではなくて、テキストエディアや他のプログラミング言語でもそのまま使える知識になります。以下の記事がおすすめで、僕もこの記事を読んで勉強しました。あとで是非読んでみてください。
輿石さん:では、今回はここまでです。長かったRの勉強も次回で最終回ですよ!最後まで一緒に頑張りましょう。
小幡:はい、いよいよ次がラストなんですね…!最後までよろしくお願いします。本日もありがとうございました!
まとめ
今回は、データの変形ができるdplyrパッケージの中で使う、文字列の操作を行うパッケージ「stringr」について勉強しました。
・文字列を連結したいときは、str_c()
を使う。
・文字列が含まれるかの判定をしたいときは、str_detect()
を使う。
・文字列を置換したいときはstr_replace()
を使う。
文字列を変形という作業は、最初は何に使うのか分かりませんでしたが、商品の名称に含まれる条件を指定したりと、幅広く使える方法でした。dplyrのfilterやmutateと組み合わせることで幅広いことができることもわかりました。
長かったRの勉強会も次回でいよいよ最終回。ラストは日付データの変形について勉強します。最後まで頑張ります!
メールマガジン登録
最新調査やマーケティングに役立つ
トレンド情報をお届けします
大学でマーケティングを勉強しながら、ヴァリューズでインターンとして働いていました。2020年の春からは新卒としてヴァリューズに入社しました。