2 データ整理と要約
第4章は、データを読み込んで、形を整えて、特徴をつかむためのデータを要約する方法を学びます。 Rは最初から用意されている基本関数だけでも十分強力ですが、外部で開発された様々なパッケージを使うと、さらに便利にデータ操作ができます。
はじめに外部のパッケージをインストールしてRの機能を拡張します。 Rでデータを操作する際に超強力なパッケージ群であるtidyverseを読み込みます。 あとで使うExcelファイルを読み込むためのreadxlパッケージも一緒に読み込みます。
テキストでは、install.packages()関数でパッケージをインストールして、library()関数で読み込んでいますが、ここではpacmanパッケージを使って、必要なパッケージを一括でインストールおよび読み込みをします。
まず始めに、pacmanパッケージをインストールします。 この作業はここで1回だけです。以降はこのコードは実行しなくて大丈夫です。
次に、pacmanパッケージのp_load()関数を使って、tidyverseとreadxlパッケージをインストールおよび読み込みします。 ::という記法はパッケージ名を明示的に指定して関数を使う方法ですが、ここではpacmanパッケージのp_load()関数を指定して使っています。
一度pacmanパッケージをインストールすれば、あとはpacman::p_load()関数を使うだけで、指定したパッケージがインストールされていなければ自動的にインストールし、読み込みまで行ってくれます。非常に便利です。
作業ディレクトリ内のdataフォルダに入っている2022idpos.csvというデータを読み込みます。 何を読み込んだのか確認するために、基本関数head()でデータの先頭6行を表示させます。
readrパッケージのread_csv()関数は、CSV形式のデータを読み込むための関数です。na = "."という引数は、データ内の.を欠損値として扱うことを指定しています。
読み込んだデータに含まれている変数の名前を確認するため、基本関数names()を使います。
データの詳細を確認するため、tidyverseのdplyrパッケージのglimpse()関数を使います。
Rows: 3,000
Columns: 4
$ id <dbl> 12, 32, 30, 29, 46, 44, 44, 32, 3, 34, 36, 3, 42, 18, 38, 4, 19…
$ date <chr> "2019/9/25", "2019/9/10", "2019/9/9", "2019/9/4", "2019/9/10", …
$ spent <dbl> 14326, 10232, 6881, 6365, 7595, 7858, 9405, 1821, 8375, 1828, 6…
$ coupon <dbl> 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, …
Excelのようにデータを表示させたいなら、基本関数View()を使います。
これで、自分が読み込んだデータがどんなものかを確認できました。
2.0.1 dplyrでデータの要約
tidyverseのdplyrパッケージを使うと、データの要約が簡単にできます。 ここでは、
-
summarise()関数:データの要約統計量を計算する -
mutate()関数:新しい変数を作成する -
filter()関数:条件に合う行を抽出する -
select()関数:特定の変数を抽出する -
arrange()関数:データを並び替える -
rename()関数:変数の名前を変更する
の使い方を学びます。
先ほど読み込んだデータには、
-
id: 顧客ID -
date: 購入日 -
spent: 購入金額 -
coupon: クーポン利用の有無 (1=利用,0=未利用)
という4変数に3,000の観測値が含まれています。 このデータをdplyrの各関数を使って、以下のような操作をしてみます。
まず、
-
select()関数でspentとcouponを抽出し、 -
arrange()関数でspentの降順に並び替え、 -
mutate()関数でspentの金額を10倍したspent10という新しい変数を作成し、 -
filter()関数でspent10が10000以上の行を抽出し、 -
summarise()関数でspent10の平均を計算し、 -
rename()関数でspent10をspent_times10に名前変更する、
という一連の操作を行います(この処理に意味はないです。)
すると、spentを10倍して、1万円以上の買い物の平均値を計算した結果がspent_times10として表示されます。
このように加工したデータをcsvファイルとして保存するには、readrパッケージのwrite_csv()関数を使います。 write_csv()関数は、第1引数に保存したいデータフレームを指定し、第2引数に保存先のファイルパスを指定します。
2.0.2 企業データの処理
MS Excelのファイルを読み込むこともできます。 ここでは、readxlパッケージのread_xlsx()関数を使って、企業データを読み込みます。
Rows: 1,431
Columns: 31
$ fyear <dbl> 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018,…
$ legalname <chr> "ハウステンボス株式会社", "ハウステンボス株式会社", "ハウステンボス株式会社", "ハ…
$ ind_en <chr> "Miscellaneous Services", "Miscellaneous Servic…
$ parent <chr> "エイチ・アイ・エス", "エイチ・アイ・エス", "エイチ・アイ・エス", "エイチ・アイ・…
$ fiscal_month <chr> "2011/10", "2012/10", "2013/10", "2014/10", "20…
$ current_liability <dbl> 65509, 76206, 85459, 98384, 122993, 102805, 131…
$ ltloans <dbl> 0, 4781, 23411, 22780, 14319, 77042, 101603, 11…
$ total_liability <dbl> 73428, 96734, 125233, 179036, 194254, 237245, 3…
$ current_assets <dbl> 102810, 111697, 137515, 196789, 212979, 233531,…
$ ppent <dbl> 12383, 40554, 45511, 48704, 60761, 62291, 83001…
$ total_assets <dbl> 139018, 173497, 215913, 281332, 308245, 332385,…
$ net_assets_per_capital <dbl> 65589, 76763, 90680, 102295, 113990, 95139, 111…
$ sales <dbl> 380805, 431483, 479478, 523246, 537456, 523705,…
$ sga <dbl> 61158, 65654, 69953, 80033, 88284, 90769, 98822…
$ operating_profit <dbl> 9407, 11316, 11843, 15906, 19970, 14274, 15915,…
$ net_profit <dbl> 8958, 10881, 11190, 11271, 14025, 1305, 15835, …
$ pnet_profit <dbl> 8300, 9331, 8903, 9050, 10890, 267, 13259, 1106…
$ re <dbl> 47658, 55966, 63664, 71612, 82150, 80988, 92731…
$ adv <dbl> 8565, 9691, 10694, 11665, 12969, 12647, 12371, …
$ labor_cost <dbl> 30624, 31743, 31648, 37240, 40402, 41505, 46583…
$ rd <dbl> 0, 0, 0, 0, 0, 0, 0, 176, 0, 0, 0, 0, 0, 0, 0, …
$ other_sg <dbl> 14962, 17078, 19720, 21648, 24043, 24710, 26067…
$ emp <dbl> 6265, 8310, 9026, 9652, 10143, 10845, 13510, 13…
$ temp <dbl> 1751, 2470, 2750, 3071, 3469, 3535, 3422, 3179,…
$ tempratio <dbl> 0.2184381, 0.2291280, 0.2335258, 0.2413739, 0.2…
$ indgrowth <dbl> -0.0038649153, 0.0780674898, -0.0737512474, 0.0…
$ adint <dbl> 0.02249183, 0.02245975, 0.02230342, 0.02229353,…
$ rdint <dbl> 0.0000000000, 0.0000000000, 0.0000000000, 0.000…
$ mkexp <dbl> 0.16060188, 0.15215895, 0.14589408, 0.15295482,…
$ op <dbl> 0.02470293, 0.02622583, 0.02469978, 0.03039870,…
$ roa <dbl> 0.0597044987, 0.0537819098, 0.0412342008, 0.032…
31変数1,431観測値からならなるデータが読み込まれました。 必要な変数が変わることもあるかと思うので、先に必要な変数名をベクトルvars_listとして保存しておきます。
必要な観測値と変数だけを抽出して、新しい変数を作成し、並び替えて、新しいオブジェクトfirm2018_checkに格納します。
# 必要な変数のリスト
vars_list <- c("legalname", "fyear", "sales", "labor_cost", "emp", "temp")
# コード4-28
firm2018_check <- firmdata |>
filter(fyear == 2018) |> # 2018年のデータを抽出
select(all_of(vars_list)) |> # 必要な変数を選択
mutate(wage = labor_cost / (temp + emp), na.rm = TRUE) |> # 新しい変数wageを作成
# temp + empが0でないチェックが必要かも
arrange(desc(wage)) # wageの降順に並び替え
head(firm2018_check, n = 10) # 先頭10行を表示all_of()関数は、select()関数内で使用され、変数名のベクトルを指定して、その変数を選択するために使います。これにより、変数名が動的に指定でき、コードの柔軟性が向上します。
2.1 4 データの要約
大規模データをあつかうとき、データそのものを眺めていても特徴をつかむことは難しいので、そのデータを特徴付ける代表値を計算して、そこからデータの特徴をつかみます。 主要な代表値として、平均値(mean)や中央値(median)、散らばり具合を示す分散(variance)や標準偏差(standard deviation)があります。 それぞれ、
を使って計算します。
先ほどのデータfirm2018_checkのsales変数について、代表値を計算してみます。 ここでは、dplyrパッケージのsummarize()関数を使って、平均、中央値、分散、標準偏差を一度に計算します。
2.2 カテゴリ変数の要約
つぎに、データ上は数値や文字列として記録されていても、その数値や文字列がカテゴリーを表す名義尺度(nominal scale)である場合を考えましょう。
例えば、firmdataの中の産業分類を表すind_en変数は、文字列で表現されていますが、その文字列はある観測値が所属するカテゴリーを表しています。 fyear変数は数値ですが、これは年度を表すカテゴリー変数です。
カテゴリ内の観測値の頻度を確認するには基本関数table()を使いますが、ここでは、dplyr::count()関数を使って、ind_en変数の各カテゴリーの頻度を計算し、gtパッケージで表形式で表示します。
| ind_en | 企業数 |
|---|---|
| Air Transportation | 8 |
| Amusement Services | 4 |
| Bakery Products | 1 |
| Communication Services | 2 |
| Cosmetics & Toilet Goods | 3 |
| Department Stores | 7 |
| Foods, NEC | 1 |
| Home & Pre-Fabs | 2 |
| Hotels | 5 |
| Miscellaneous Services | 27 |
| Miscellaneous Wholesales | 2 |
| Motor Vehicles | 4 |
| Musical Instrument | 1 |
| Railroad (Major) | 27 |
| Railroad (Minor) | 2 |
| Real Estate - Sales | 1 |
| Retail Stores, NEC | 35 |
| Supermarket Chains | 14 |
| Trucking | 1 |
教科書のようにtable()関数を使うと簡単に頻度表が作れますが、オブジェクトの型がtable型になってしまい、後で他の処理に使いにくくなるので、data.frame型のまま処理できるdplyr::count()関数を使いました。
広告費を表すadient変数の観測値が全企業の中央値より大きいか否か、でダミー変数ad_dummyを作成し、ind_en変数とad_dummy変数のクロス集計表を作成します。
教科書を先取りしますが、if_else()関数を使ってダミー変数を作成し、count()関数でクロス集計表を作成し、gtパッケージで表形式で表示します。 tidyrパッケージのpivot_wider()関数を使って、クロス集計表が見やすくなるように、縦は産業名、横は広告費が中央値より大きいか否か、で表示するように変形しています。
table()関数を使うと簡単にクロス集計表が作れますが、あえてdata.frame型のまま処理できるdplyr::count()関数を使っています。そのため、データが矩形データのまま扱えるので、後で他の処理に使いやすくなるものの、表にすると見にくくなるので、tidyr::pivot_wider()関数で見やすく変形しています。
| ind_en | 0 | 1 |
|---|---|---|
| Air Transportation | 4 | 4 |
| Amusement Services | 4 | 0 |
| Bakery Products | 0 | 1 |
| Communication Services | 1 | 1 |
| Cosmetics & Toilet Goods | 0 | 3 |
| Department Stores | 0 | 7 |
| Foods, NEC | 0 | 1 |
| Home & Pre-Fabs | 0 | 2 |
| Hotels | 5 | 0 |
| Miscellaneous Services | 17 | 10 |
| Miscellaneous Wholesales | 1 | 1 |
| Motor Vehicles | 0 | 4 |
| Musical Instrument | 0 | 1 |
| Railroad (Major) | 27 | 0 |
| Railroad (Minor) | 2 | 0 |
| Real Estate - Sales | 0 | 1 |
| Retail Stores, NEC | 11 | 24 |
| Supermarket Chains | 2 | 12 |
| Trucking | 1 | 0 |
次に、カテゴリ変数ごとに処理を行いたい場合、例えば、産業分類ごとに売上高の平均値や標準偏差を計算したい場合は、 summarise()関数やmutate()関数の中で、.by =という引数を使って、グループ化して処理することができます。
コード4-39
firm2018 |>
summarize(
.by = ind_en, # 産業分類ごとに以下を計算
obs = n(), # 観測数
sales_m = mean(sales), # 売上高の平均値
sales_sd = sd(sales), # 売上高の標準偏差
adint_m = mean(adint), # 広告費の平均値
adint_sd = sd(adint) # 広告費の標準偏差
) |>
gt::gt() |> # 表形式で表示
gt::fmt_number( # 数値の表示形式を指定
columns = c(sales_m, sales_sd, adint_m, adint_sd),
decimals = 2
) |>
gtExtras::gt_theme_538() # 表のテーマを指定| ind_en | obs | sales_m | sales_sd | adint_m | adint_sd |
|---|---|---|---|---|---|
| Miscellaneous Services | 27 | 311,867.22 | 456,036.57 | 0.01 | 0.02 |
| Amusement Services | 4 | 298,137.50 | 263,017.35 | 0.00 | 0.00 |
| Foods, NEC | 1 | 504,153.00 | NA | 0.02 | NA |
| Retail Stores, NEC | 35 | 571,019.17 | 547,246.68 | 0.02 | 0.03 |
| Air Transportation | 8 | 1,772,786.50 | 305,239.60 | 0.00 | 0.00 |
| Supermarket Chains | 14 | 4,335,164.07 | 3,511,346.59 | 0.01 | 0.01 |
| Bakery Products | 1 | 1,059,442.00 | NA | 0.01 | NA |
| Railroad (Major) | 27 | 1,302,920.52 | 1,037,834.05 | 0.00 | 0.00 |
| Hotels | 5 | 62,134.80 | 58,060.28 | 0.00 | 0.00 |
| Real Estate - Sales | 1 | 1,861,195.00 | NA | 0.01 | NA |
| Home & Pre-Fabs | 2 | 4,143,505.00 | 0.00 | 0.01 | 0.00 |
| Railroad (Minor) | 2 | 260,502.00 | 0.00 | 0.00 | 0.00 |
| Communication Services | 2 | 547,087.50 | 172,735.58 | 0.02 | 0.03 |
| Miscellaneous Wholesales | 2 | 176,520.00 | 52,778.45 | 0.02 | 0.03 |
| Trucking | 1 | 1,118,094.00 | NA | 0.00 | NA |
| Musical Instrument | 1 | 434,373.00 | NA | 0.04 | NA |
| Department Stores | 7 | 843,248.29 | 348,818.99 | 0.02 | 0.01 |
| Motor Vehicles | 4 | 5,279,121.75 | 4,233,187.72 | 0.03 | 0.00 |
| Cosmetics & Toilet Goods | 3 | 140,669.33 | 100,063.48 | 0.11 | 0.05 |
ggplot2パッケージを使って、散布図を描いてみましょう。 ggplot2パッケージのggplot()関数は、主に次の引数を使います。
-
data:データフレームを指定 -
mapping:美的(aesthetic)マッピングを指定。aes()関数を使って、x軸とy軸にどの変数を割り当てるかを指定します。 -
geom_***: グラフの種類を指定。ここでは、geom_point()で散布図を描き、geom_smooth(method = lm)で線形回帰モデルをフィットさせた線を描きます。
たくさんのオプションが用意されていますが、まずは基本的な使い方を覚えましょう。
散布図を描く
引数名を省略したり、+の位置を変えたりしても、同じグラフが描けます。 通常はこれくらい簡略化して書くことが多いです。
変数AとBをもつデータフレームABを作成して、相関係数を計算し、散布図を描いてみましょう。
2.3 簡易的なデータ探索法紹介
2.3.1 データ処理と顧客関係管理(CRM)
データの中身を確認すると、4つの変数id、date、spent、couponが含まれており、それぞれの方が
-
id:数値型 -
date:文字列 -
spent:数値型 -
coupon:数値型
となっています。 本来はidは顧客を表すカテゴリー変数で、dateは日付を表す変数、couponはクーポン利用の有無を表すダミー変数なので、これらの変数の型を適切に変換しておきましょう。
Rows: 3,000
Columns: 4
$ id <fct> 12, 32, 30, 29, 46, 44, 44, 32, 3, 34, 36, 3, 42, 18, 38, 4, 19…
$ date <date> 2019-09-25, 2019-09-10, 2019-09-09, 2019-09-04, 2019-09-10, 20…
$ spent <dbl> 14326, 10232, 6881, 6365, 7595, 7858, 9405, 1821, 8375, 1828, 6…
$ coupon <fct> クーポン使用, クーポン使用, クーポン使用, クーポン未使用, クーポン使用, クーポン未使用, クーポン未使用, クーポン…
次に、date変数を使って、ある基準日から何日前の来店かを表すdatediffという新しい変数を作成します。 ここでは、基準日を2019年10月2日に設定して、date変数の日付から基準日までの差を計算して、datediff変数に格納します。
コード4-48
次に、顧客ごとに、来店頻度(frequency)、累計購入金額(monetary)、クーポン利用回数(cherry)、最終来店からの経過日数(recency)を計算して、新しいデータフレームidpos_custに格納します。
コード4-49
Rows: 1,487
Columns: 5
$ id <fct> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 1…
$ frequency <int> 21, 20, 21, 19, 20, 19, 17, 20, 24, 16, 14, 25, 19, 21, 31, …
$ monetary <dbl> 173314, 157976, 134673, 154416, 177156, 151853, 98428, 16863…
$ cherry <int> 8, 13, 5, 10, 11, 11, 10, 12, 14, 5, 3, 15, 13, 14, 13, 11, …
$ recency <dbl> 1, 2, 1, 4, 3, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 2, …
idpos_custデータフレームには、顧客ごとに、来店頻度(frequency)、累計購入金額(monetary)、クーポン利用回数(cherry)、最終来店からの経過日数(recency)が格納されました。 その結果、観測値が1,487件、変数が5つのデータフレームができました。
合計購入金額(monetary)の高い順に並び替えます。 降順に並び替えるには、arrange()関数の中で、desc()関数を使います。
2.3.2 データの結合
2つのデータフレームを結合する方法を学びましょう。 データフレームの結合は基本関数でもできますが、tidyverseのdplyrパッケージのJOIN関数が便利です。 dplyrパッケージのJOIN関数には、以下の4つがあります。
-
inner_join(): 内部結合 -
left_join(): 左外部結合 -
right_join(): 右外部結合 -
full_join(): 完全外部結合
それぞれの結合の結果をベン図で表すと以下のようになります。
データの結合のイメージ図は以下の通りです(この図は、Hadley Wickham, Mine Çetinkaya-Rundel, and Garrett Grolemundによる”R for Data Science”の図を引用しています)。
内部結合は、2つのデータフレームの共通の変数の値が一致する行のみを抽出するため、結合後のデータフレームが非常に小さくなりがちであるため、ほとんど使用しません。 最も利用する可能性が高いのは、結合元のデータフレームのすべての行を残す左外部結合です。 これはdplyr::left_join()で実行できます。
left_join()の引数は、以下の通りです。
-
x: 結合元のデータフレーム -
y: 結合するデータフレーム -
by: 結合する変数
JOIN関数で2つのデータフレームを結合するときは、1つ以上のキーとなる変数が、結合元のデータフレームと結合するデータフレームの両方に存在し、またキーの値により行が一意に決まる、つまり観測値がユニークになっていることが重要です。
結合するデータフレームid_data.csvを読み込んで、id_dataという名前のデータフレームに格納しましょう。 変数idは顧客IDを表すカテゴリー変数なので、読み込むときにas.factor()関数を使って、id変数をカテゴリー変数に変換しておきます。
tibble [3,000 × 4] (S3: tbl_df/tbl/data.frame)
$ id : Factor w/ 3000 levels "1","2","3","4",..: 1 2 3 4 5 6 7 8 9 10 ...
$ gender : chr [1:3000] "female" "male" "female" "female" ...
$ age : num [1:3000] 51 38 41 24 48 46 36 30 26 57 ...
$ famsize: num [1:3000] 2 2 1 1 5 1 1 1 3 5 ...
3000件の観測値と4つの変数からなるデータフレームid_dataが読み込まれました。 これをidpos_custと左外部結合して大きくなったデータフレームをidpos_custに上書きして格納します。
購買金額monetaryの上位顧客20人中の男女比を確認するには、gender変数の頻度を確認します。
monetary変数を10等分して、各顧客がどのランクに属するかを表す変数decile_rankを作成します。 cut()関数を使って、monetary変数を10等分して、各顧客がどのランクに属するかを表す変数decile_rankを作成します。
cut()関数は、数値を指定した区間に分割して、カテゴリ変数を作成するための関数です。 cut()関数の引数は、
-
x: 分割する数値ベクトル -
breaks: 分割する区間の境界を指定する数値ベクトル -
labels: 分割された区間に対応するラベルを指定するベクトル -
include.lowest: 最初の区間に最小値を含めるかどうかを指定する論理値 -
right: 区間の右端を含めるかどうかを指定する論理値 -
dig.lab: ラベルに使用する小数点以下の桁数を指定する整数 -
ordered_result: 結果のカテゴリ変数を順序付きにするかどうかを指定する論理値
となり、最初の2つを指定すれば、数値を区間に分割して、カテゴリ変数を作成することができます。 この分割する区間を指定するとき、quantile()関数を使って、monetary変数の10分位点を計算して、その分位点を区間の境界として指定しています。
quantile()関数の引数は、
-
x: 分位点を計算する数値ベクトル -
probs: 分位点を指定する数値ベクトル。0から1の範囲で指定します。 -
na.rm: 欠損値を除外して計算するかどうかを指定する論理値 -
names: 結果の分位点に名前を付けるかどうかを指定する論理値 -
type: 分位点の計算方法を指定する整数。1から9のいずれかを指定します。詳しくは?quantileで確認してください。
ここでは、monetary変数を10等分するために、quantile()関数を使って、monetary変数の10分位点を計算しています。
decile_rank変数が追加されています。 次に、decile_rank変数ごとに、顧客の頻度と累計購入金額の合計を計算して、decileという新しいデータフレームに格納します。
次に、decileデータフレームに、累計購入金額の合計に対する各ランクの累計購入金額の割合を表すpercent変数を追加して、decile2という新しいデータフレームに格納します。
コード4-57
もっとも多くの購入金額を占めるグループ1が、全体購入金額の約45%を占めていることが分かりました。
2.4 補論:ワイド型とロング型
テキストで用いられているgather()関数は非推奨なので、 推奨されているtidyrパッケージのpivot_longer()関数を使って、データを縦長に変形してみましょう。
names_prefix引数を使うと、変形の対象となる列名から指定した文字列を削除して、names_toで指定した変数に格納することができ、教科書のコード4-58の処理が不要になります。
次に、tidyrパッケージのpivot_wider()関数を使って、データを横長に変形してみましょう。 Rは変数名に数字から始まる文字列を使うことはできないため、names_prefix引数を使って、変形の対象となる列名にsalesを付加して、names_toで指定した変数に格納するようにします。
ロング型で加工したデータをワイド型にして、knitrパッケージのkable()関数やgtパッケージのgt()関数で表形式で表示させることができます。




