前回までの進捗
こんにちは、小幡です。Rを学んでおります。
前回は「dplyr」パッケージを使ったデータ集計の7つの処理のうち、6つまでを勉強しました。列の追加、行のフィルター、データの要約とデータのグループ化などを組み合わせることで、1つのデータフレームなら自由に操作ができるようになった気がします。
今回は、前回と同じ「dplyr」パッケージから、Joinを行う関数について勉強します。Joinができると複数のデータを組み合わせて分析ができるようになるみたいです。また、「パイプ演算子」という、dplyrを使ったコーディングをよりシンプルに書けるやり方も教えていただけるそうです。
では輿石さん、よろしくお願いいたします!
データの結合
輿石さん:今まで1つのデータフレームに対して列や行を増やす、減らす、並び替える、要約するといった処理を勉強しました。今までやった6つの関数を組み合わせることで、1つのデータであれば小幡さんはかなり自由にデータを加工・集計できるようになっているはずです。
今回は複数のデータフレームを結合するという操作について勉強します。実際にデータ分析をしていると、まずはいくつかのデータを1つにまとめるという作業から始まることも少なくありません。このまとめる/結合する操作をjoinと言い、「データをjoinする」とか表現したりします。
小幡:具体的に、どんなときにデータの結合が必要になるんでしょうか?
輿石さん:例えばある売上データで「9月1日に商品Aが1個売れた」というレコードがあったとします。しかし売上データには商品名がIDでAとしか書いていないので、これだけではAがどんな商品なのかは分かりません。どんな商品なのかを知るためには、商品マスタのデータと照らし合わせて、2つのデータを結合しなければいけません。
他には、売上データには購入者の情報がユーザIDしかなくて、買った人の性年代や居住地などの属性情報を組み合わせて分析するために、ユーザマスタを結合したいケースも多いのではと思います。
小幡:なるほど!
輿石さん:まずはleft_join()とinner_join()の2つの関数を理解するのが良いでしょう。では、今日の内容を理解するために使うデータフレームを読み込みましょう。今まで使っていた売上データと、ユーザの属性情報が記録されたユーザマスタの2つを使います。次のコードをテキストエディタにコピペして実行してみてください。
# packageの読み込み library(tidyverse) # 売上データの読み込み kenko_ec_data <- read_tsv("https://adcreative.valuesccg.net/manamina/article/manaminar/kenko_ec_sample.tsv") # 記事中での表示を見やすくするために列を絞っておきます。 kenko_ec_data_s <- select(kenko_ec_data, userid, purchase_date, item_name)●コンソールの出力内容
#> # A tibble: 10,361 x 3
#> userid purchase_date item_name
#> <chr> <date> <chr>
#> 1 K00002 2020-01-07 マルチ酵素 サプリメント お徳用 90日分
#> 2 K00003 2020-03-24 カルシウム サプリメント プレミアム 30日分
#> 3 K00005 2020-01-30 クレアチン サプリメント スタンダード 30日分
#> 4 K00008 2020-01-30 乳酸菌 サプリメント プレミアム 30日分
#> 5 K00009 2020-01-15 クエン酸 サプリメント スタンダード 30日分
#> 6 K00010 2020-03-22 ビタミンB サプリメント お徳用 90日分
#> 7 K00019 2020-03-21 グルコサミン サプリメント お徳用 90日分
#> 8 K00023 2020-01-05 ビタミンB サプリメント プレミアム 30日分
#> 9 K00027 2020-03-13 スピルリナ サプリメント お徳用 90日分
#> 10 K00031 2020-02-12 BCAA サプリメント スタンダード 30日分
#> # ... with 10,351 more rows
●コード
# ユーザマスタデータの読み込み
user_master <- read_tsv("https://adcreative.valuesccg.net/manamina/article/manaminar/user_master.tsv")
user_master
●コンソールの出力内容
#> # A tibble: 2,000 x 4
#> userid gender age pref
#> <chr> <chr> <chr> <chr>
#> 1 K00002 02.女性 59 12.千葉県
#> 2 K00003 02.女性 53 13.東京都
#> 3 K00008 02.女性 47 13.東京都
#> 4 K00010 01.男性 35 27.大阪府
#> 5 K00023 01.男性 36 13.東京都
#> 6 K00031 01.男性 53 23.愛知県
#> 7 K00038 01.男性 47 11.埼玉県
#> 8 K00039 01.男性 55 01.北海道
#> 9 K00061 01.男性 29 13.東京都
#> 10 K00064 01.男性 71 13.東京都
#> # ... with 1,990 more rows
左のデータに右のデータをくっつける(left_join)
輿石さん:データの準備ができましたね。まずは、基本となるleft_joinを習得しましょう。今まで学習したselectやsummariseなど6つのデータフレーム操作のための関数は、必ず第一引数にデータフレームを指定していました。joinは2つのデータフレームをくっつける動きをするのでleft_join(データフレーム1, データフレーム2, by = "キーとする列")
と、データフレームを2つ引数に取ります。by
という引数には、「何を基準に2つのデータを結合するのか?」を指定してあげます。今回の場合はuseridを基準にデータをくっつけたいので、引数byにはuseridを指定して実際に結合してみましょう。
left_joined_data <- left_join(kenko_ec_data_s, user_master, by = "userid") left_joined_data●コンソールの出力内容
#> # A tibble: 10,361 x 6
#> userid purchase_date item_name gender age pref
#> <chr> <date> <chr> <chr> <chr> <chr>
#> 1 K00002 2020-01-07 マルチ酵素 サプリメント お徳用 90日分 02.女性 59 12.千葉県
#> 2 K00003 2020-03-24 カルシウム サプリメント プレミアム 30日分 02.女性 53 13.東京都
#> 3 K00005 2020-01-30 クレアチン サプリメント スタンダード 30日分 NA NA NA
#> 4 K00008 2020-01-30 乳酸菌 サプリメント プレミアム 30日分 02.女性 47 13.東京都
#> 5 K00009 2020-01-15 クエン酸 サプリメント スタンダード 30日分 NA NA NA
#> 6 K00010 2020-03-22 ビタミンB サプリメント お徳用 90日分 01.男性 35 27.大阪府
#> 7 K00019 2020-03-21 グルコサミン サプリメント お徳用 90日分 NA NA NA
#> 8 K00023 2020-01-05 ビタミンB サプリメント プレミアム 30日分 01.男性 36 13.東京都
#> 9 K00027 2020-03-13 スピルリナ サプリメント お徳用 90日分 NA NA NA
#> 10 K00031 2020-02-12 BCAA サプリメント スタンダード 30日分 01.男性 53 23.愛知県
#> # ... with 10,351 more rows
小幡:売上データに属性情報がくっついてますね!ユーザマスタを見るとK00002さんという人は、千葉県の59才の女性でした。確かに正しく紐づいていますね。
輿石さん:そうですね。left_join()はbyに指定した値が同じ行同士をくっつけてくれるんです。
小幡:3行目のK00005さんは属性が”NA”となっていますね。これはどういうことですか??
輿石さん:良いところに気が付きましたね。NA
はRで欠損値と呼ばれる特殊な値で、「データがないよ」ということを表しています。
実は、売上データは3000人のデータから構成されているのですが、ユーザマスタにはその中の2000人分しかデータが含まれていなかったんです。購入時にユーザ登録してくれた人やアンケートに答えてくれた人しかユーザの属性情報がないというのはよくあるのではと思います。
left_joinは、「左のデータを基準に右のデータをくっつける」ので、左のデータはすべて残るんです。何も紐づかなかった箇所にはNA
という欠損を表す値が入っています。
小幡:NAはデータがない=欠損していることを表す値だったんですね。ちなみに「左のデータ」というのは第一引数に指定したデータで、「右のデータ」というのは第二引数に指定したデータ、ということであってますか?
輿石さん:その通りです!
共通する行だけを残すinner_join
輿石さん:次にinner_join()を説明します。関数の引数はleft_join()と一緒です。
小幡:inner_join(データフレーム1, データフレーム2, by = "キーとする列")
ですね。left_join()とinner_join()で何が違うんでしょう?
輿石さん:そこですよね。left_joinは"left"と名前にあるように、「左のデータを基準に右のデータを結合する」という働きをしました。左が基準なので左のデータはすべて残って、右側のデータが紐づかない箇所には欠損を表すNA
が入っていましたね。
inner_join()は「2つのデータ両方に含まれる値だけ残して結合する」という働きをします。下記はinner_joinの動きを表す図です。x1という列に着目してください。aとb両方のデータに含まれるのはAとBです。CとDはどちらか片方にしか存在しません。なので、inner_join()の結果はAとBだけのレコードに絞られていますね。
輿石さん:ではサンプルデータで実行してみましょう。
inner_joined_data <- inner_join(kenko_ec_data, user_master, by = "userid") inner_joined_data●コンソールの出力内容
#> # A tibble: 6,960 x 6
#> userid purchase_date item_name gender age pref
#> <chr> <date> <chr> <chr> <chr> <chr>
#> 1 K00002 2020-01-07 マルチ酵素 サプリメント お徳用 90日分 02.女性 59 12.千葉県
#> 2 K00003 2020-03-24 カルシウム サプリメント プレミアム 30日分 02.女性 53 13.東京都
#> 3 K00008 2020-01-30 乳酸菌 サプリメント プレミアム 30日分 02.女性 47 13.東京都
#> 4 K00010 2020-03-22 ビタミンB サプリメント お徳用 90日分 01.男性 35 27.大阪府
#> 5 K00023 2020-01-05 ビタミンB サプリメント プレミアム 30日分 01.男性 36 13.東京都
#> 6 K00031 2020-02-12 BCAA サプリメント スタンダード 30日分 01.男性 53 23.愛知県
#> 7 K00038 2020-03-18 カルシウム サプリメント プレミアム 30日分 01.男性 47 11.埼玉県
#> 8 K00039 2020-01-09 ビタミンB サプリメント プレミアム 30日分 01.男性 55 01.北海道
#> 9 K00061 2020-02-05 EPA サプリメント スタンダード 30日分 01.男性 29 13.東京都
#> 10 K00064 2020-01-30 オルニチン サプリメント お徳用 90日分 01.男性 71 13.東京都
#> # ... with 6,950 more rows
小幡:left_join()の結果は10,361行でしたが、inner_joinでは6,960行と少なくなっています。そしてNA
という値もないですし、K00005さんなどユーザマスタにいない人のデータが消えてますね。2つのデータ両方にいる人に絞られているので行数が減っているんですね。
輿石さん:何か実行した後に行数を確認するのは良い習慣ですね!その通りです!
ヴァリューズオフィスの11F「Peak Bar」にて(2020年2月に撮影)
何を基に結合するか?キーの指定の仕方
輿石さん:2つのデータを結合する際に、どの列をキーにするかは時によって違いますよね。また、キーが複数あるケースや、データによって列名が違う場合があったりします。その指定の仕方を簡単に紹介します。
小幡:キーが複数でもできるんですね。列名が違うっていうのはどういうことでしょう?
輿石さん:例えば、売上データはuseridという小文字の列名で、ユーザマスタはUSERIDと大文字だったり、user_idとアンダースコアが入っている列名になっていたりと、名前が違っているケースが結構あるんです。こういう場合でも「useridとuser_idがキーですよ」というのを機械に教えてあげる必要があります。
以下の表にまとめました。複数キーがある場合はc()という複数の要素をまとめる関数で囲ってあげます。名前が違う場合はイコールでつないであげましょう。
-- | -- |
名前が同じ一つの列でjoin | by = "id" |
名前が同じ複数の列でjoin | by = c("id1","id2") |
名前が違う一つの列でjoin | by = c("id1" = "ID1") |
名前が違う複数の列でjoin | by = c("id1" = "ID1", "id2" = "ID2" ) |
小幡:いろいろ方法があるんですね…!名前も違って複数のキーを使うケースは結構ややこしいですね...。
輿石さん:そうですね(笑) 書き方そのものを覚えなくてもいいので、こういった指定で解決できることを知っておいてください。最初は必要になったらこの表を参照するでOKですよ。徐々に覚えましょう!
パイプ演算子を使った書き方
輿石さん:これでdplyrのデータフレームを操作する関数7つすべてを学びました!お疲れ様です。
小幡:ありがとうございました!
輿石さん:dplyrの学習の最後に、「パイプ演算子」を使ってコードを書くことで、これら7つを組み合わせたコードが格段に書きやすく、そして読みやすくなることを紹介します。
輿石さん:パイプ演算子を学ぶ前に、今まで通りのやり方で男性に絞った商品カテゴリごとの売上ランキングを集計してみましょう。
# 性別の集計のためにユーザマスタを結合する。 kenko_ec_data_joined <- left_join(kenko_ec_data, user_master, by = "userid") # filter関数で男性のデータに絞る kenko_ec_data_joined_f <- filter(kenko_ec_data_joined, gender == "01.男性") # mutate関数で、購入個数と値段を掛け合わせた支払い金額列を追加する。 kenko_ec_data_joined_f_m <- mutate(kenko_ec_data_joined_f, payment = price * purchase_amount) # 商品カテゴリでデータフレームをグループ化する。 kenko_ec_data_joined_f_m_grouped <- group_by(kenko_ec_data_joined_f_m, item_category) # summarise関数で商品カテゴリごとの売り上げ金額を集計する。 kenko_ec_data_joined_sum <- summarise(kenko_ec_data_joined_f_m_grouped, payment = sum(payment)) # arrange関数で売り上げ金額の降順で並び替える。 kenko_ec_data_joined_sum_sort <- arrange(kenko_ec_data_joined_sum, desc(payment)) kenko_ec_data_joined_sum_sort●コンソールの出力内容
#> # A tibble: 54 x 2
#> item_category payment
#> <chr> <dbl>
#> 1 ホエイプロテイン 5576655
#> 2 BCAA 2134305
#> 3 マルチビタミン&ミネラル 1166752
#> 4 乳酸菌 1008216
#> 5 コラーゲン 775000
#> 6 ルテイン 665987
#> 7 食物繊維 638780
#> 8 クエン酸 571428
#> 9 L-シトルリン 569294
#> 10 コエンザイムQ10 543062
#> # ... with 44 more rows
輿石さん:以下の図のように、「処理を加えたデータフレームを次のデータフレームの第一引数に渡す」ということを繰り返していますね。
※DFは「データフレーム」の略
輿石さん:パイプ演算子は「%>%」と書いて、「左側のオブジェクトを右側の関数の第一引数に渡す」という働きをするんです。上の処理をパイプ演算子「%>%」を使って書き直してみましょう。
kenko_ec_data %>% left_join(user_master, by = "userid") %>% filter(gender == "01.男性") %>% mutate(payment = price * purchase_amount) %>% group_by(item_category) %>% summarise(payment = sum(payment)) %>% arrange(desc(payment)) -> output output
小幡: だいぶシンプルになりましたね!
輿石さん:パイプ演算子を使うメリットは2つです。1つはそれぞれのデータフレームの名前を考えなくてよいこと。kenko_ec_data_joinedなど、一つの処理をする度に名前を決めていましたが、パイプ演算子を使えばその必要はなくなります。2つ目は、左から右に、上から下にコードを読んでいくと処理の流れが理解できるので、書きやすく理解もしやすい点です。
上のコードだったら、kenko_ec_dataをuser_masterと結合して、性別でフィルターして、支払金額列を追加して、商品カテゴリごとに売り上げ金額を集計して、、、と順に読めませんか??
小幡: 読める気がします!
輿石さん:dplyrを使って処理を書く際は、ぜひパイプ演算子「%>%」を使ってくださいね。さて、今回はここまでです! 長かったdplyrに関する説明は今回で終了です。処理の1つ1つはすごく単純なことをやっています。しかし、組み合わせて使うことで複雑な処理もできるようになる。それを実感できるとRに取り組みやすくなると思いますよ。
まとめ
今回は、複数のデータを結合するjoinを行う関数と、コードをシンプルに書けるようになる%>%(パイプ演算子)について勉強しました。
・2つのデータフレームのうち、共通する行だけを結合したいときはinner_joinを使う。
・どちらかは全て残して結合したいときは、left_joinを使う。
・結合の際、キーにする列は複数列・もしくは列名が異なっていても結合ができる。
・%>%(パイプ演算子)を使ってコードを書くと、データフレームを操作する関数同士をつなげて処理を記述できて便利。
次回からは、関数の内部で使うもののバリエーションを増やしていきます。今は足し算・引き算などシンプルな処理しかできないですが、ここの知識が豊富だと、より難しい計算もできるようになるので、やっていきましょう!
Rで文字列の変形ができるstringrとは?マーケターが1からRを勉強します【第7回】
https://manamina.valuesccg.com/articles/814マーケター1年目の小幡さんがRを学んでいきます。講師は株式会社ヴァリューズのデータアナリスト、輿石さん。第7回はRを使ったデータ集計の方法を習得します。Rでデータを扱えるようになりたいと考えている方、ぜひ小幡さんと一緒に勉強していきましょう。
■関連記事
Rとは一体何? マーケターが1からRを勉強するドキュメンタリーが始まります【第1回】
https://manamina.valuesccg.com/articles/717マーケター1年目、小幡さんのRを学ぶ道のりが始まります。講師は株式会社ヴァリューズのデータアナリスト、輿石さん。初回はそもそもRとは何か、なぜデータ分析でRを学ぶべきなのかを学び、実際にRをインストールします。Rでデータを扱えるようになりたいと考えている方、ぜひ小幡さんと一緒に勉強していきましょう。
コードはもう書かない!?「ノーコード」のサービスやメリット・デメリットとは…検索者属性から関心層の実態も調査
https://manamina.valuesccg.com/articles/1094エンジニアではない人にとって、これまでプログラミングによるプロダクト開発は縁遠いものでした。しかし、いまや非エンジニアも簡単にプロダクトを開発できる時代になっています。それを可能にしたのが「ノーコード」。今回はそんなノーコードについてデータ分析ツール「Dockpit(ドックピット)」のキーワード分析機能を使い、関心を持つユーザーを深堀りしていきます。
メールマガジン登録
最新調査やマーケティングに役立つ
トレンド情報をお届けします
大学でマーケティングを勉強しながら、ヴァリューズでインターンとして働いていました。2020年の春からは新卒としてヴァリューズに入社しました。