トップ 履歴 一覧 Farm ソース 検索 ヘルプ RSS ログイン

データサイエンスのエッセンス


キーワード

最終更新時間:2020年02月16日 10時03分32秒
アフィリエイト・広告について
プライバシーポリシー

はじめに

この記事はJesse Cambon氏による "Data Science Essentials" を翻訳したものです。記事のライセンスは、翻訳元と同じMITライセンスです。

以下、翻訳です。


Rがデータサイエンスにおいて優れていると考えるひとつの理由として、多種多様なパッケージの存在があります。しかし、あまりに多彩すぎて、この巨大でオープンなエコシステムに踏み入ったばかりのビギナーにとっては、圧倒されてしまう面もあります。必要な情報は、確かにそこ (Rのエコシステム内) にありますが、しばしばStack Overflow内のスレッドやWebサイトに断片化されています。

これらの断片化された情報を統合する試みのひとつとして、この記事では、私 (著者) がデータサイエンティストとして日々繰り返し使う、基本的な手法を紹介したいと思います。これから紹介するコードは、あなたがRを使ってデータサイエンスの様々な活動を行う際に知っておくべき基礎的なもので、データ操作、要約、可視化の範囲をカバーしています。

私は、主にdplyrtidyrggplot2パッケージを使っています。これらのパッケージは、優れた (英語の) ドキュメントが整備されていて、詳細な仕様について理解を深めることができます。また、サンプルのデータセットも同梱されているため、外部から別途データをダウンロードする必要はありません。なお、この記事では、入力および出力を都度表示していますが、場合によっては、それぞれの最初の数行のみを表示していることもあります。

もしあなたが、記事を読みながらコードも実行したいと思うようでしたら、この記事自体を記述したRMarkdownファイルも入手できます。また、もしあなたのR環境に tidyverse パッケージがインストールされていなければ、記事のコードを実行する前に、以下のようにしてパッケージをインストールしてください。[1]

install.packages("tidyverse")

データ操作の基礎

まずはじめに、tidyverseパッケージを読み込みましょう。

library(tidyverse)

それでは、ggplot2パッケージに付属するmpgデータセットを見てみましょう。

mpg

※ 翻訳記事では画像にしています

このデータセットを使って、dplyrパッケージによる、(データサイエンスの現場で) よく使われるデータ操作の方法を紹介していきます。もし、あなたがRについてまったくの初心者である場合、以下で使われる <- という記号 (演算子) は、左辺の変数 (オブジェクト) に右辺の値 (データ) を代入するためのものであると理解してください。この記事では、mpgデータセットを操作し、結果をmpg_subsetデータセットとして保存します。

同様に、もしあなたがdplyrパッケージについて詳しくなければ、%>% 演算子は「パイプ」と呼ばれ、ある関数で処理した結果を次の関数に受け渡すためのものであると理解してください。

# 関数1の処理結果を、続けて関数2で処理する
関数1(データ) %>% 関数2

パイプ演算子を使うことで、データ操作を連続的に、理解しやすいコードで記述できます。ここでは、mpgデータセットから、「日産車 (Nissan)」「2005年以降の生産」「4気筒」という条件を満たす2行のデータを抽出します。続いて、新規に2つの列を追加し、列を並べ替えます。そして、4つの列を削除し、列名を変更します。この操作を行うために使用する関数は以下の通りです。

  • filter(): データセットから行を抽出します。この例では、& 演算子と組み合わせて使います。
  • mutate(): データセットに新しい列を追加します。[2]
  • str_c(): 文字列を結合します。この例では、それぞれ別の列として記録されているメーカー (manufacturer) と車種 (model) を結合し、ひとつの列にするために使います。
  • select(): データセットから列を選択します。この例では、まず4列を順番を指定して選択し、他の列は元のデータの通りの順番で選択します。しかし、そのうち4列については除外 (削除) します。
    • 列名の前に - を付けると、その列を削除します。
    • everything() 関数は、残りのすべての列を意味します。詳細は select() 関数のヘルパー関数についてのドキュメントを参照してください。
  • rename(): 列名を変更します。この例では、flという列をfuel_typeという名前に変更しています。
mpg_subset <- mpg %>%
  filter(cyl == 4 & year >= 2005  & manufacturer == "nissan") %>%
  mutate(mpg_ratio = hwy/cty,
         make_model = str_c(manufacturer,' ',model)) %>%
  select(make_model,year,hwy,cty,everything(),
         -manufacturer,-model,-drv,-trans) %>%
  rename(fuel_type = fl)

 訳注: コードの日本語的解釈

上記のコードの意味を、日本語の文章として捉えると、以下のようになります。

結果をmpg_subsetに格納する <- mpgデータセットについて %>%
  データからcyl列の値が4、year列の値が2005以上、manufacturerの値がnissanである行を抽出する %>%
  hwy列の値をcty列の値で割ったものをmpg_ratio列として、manufacturer列とmodel列をスペースで
  区切って結合した値をmake_model列として新たに作成・追加する %>%
  make_model、year、hwy、cty列を順番に、残りの列は元のデータの通りに選択するが、
  そのうちmanufacturer、model、drv、trans列は含まない %>%
  fl列の名前をfuel_typeに変更する

※ 翻訳記事では画像にしています

要約統計

度数 (カウント) や平均、中央値などの要約統計量を算出することは、データセットを理解するための第一歩として重要です。カテゴリ変数の値について、度数 (行数) をカウントするには、count()関数を使います。ここでは、cyl (気筒数) 列の値ごとに、度数をカウントします。

count_cyl <- mpg %>%
  count(cyl)

※ 翻訳記事では画像にしています

group_by() 関数や summarize() 関数[3]を使うと、より多様な集計が可能になります。ここでは、"class_c" という名前の新しい列を作成します。これには、"2seater" と "subcompact" に分類される車種を "subcompact" に統合した結果を格納します。これには、case_when() 関数を使います。その上で、カテゴリごとに集計し、いくつかの統計量を算出します。


※ 訳者による図です

arrange() 関数は、集計の結果を格納するために作成したcount列の降順に、行を並べ替えるために使用します。また、グループ化を解除する ungroup() 関数は、この場合必須ではないですが、この後グループ化しない状態のデータセットを扱うための練習ということで使用しています。

mpg_stats <- mpg %>% select(class,hwy) %>%
  mutate(class_c = case_when(class %in% c("2seater","subcompact") ~ "subcompact",
                               TRUE ~ class)) %>%
  group_by(class_c) %>%
  summarize(count = n(),
            min_hwy = min(hwy),
            max_hwy = max(hwy),
            median_hwy = median(hwy),
            mean_hwy = mean(hwy)) %>%
  ungroup() %>%
  arrange(desc(count)) # sort dataset

※ 翻訳記事では画像にしています

データの結合 (Stack)

もしあなたが、複数の表構造データセットを保有している場合、それらを行方向または列方向に結合できます。実際にやってみましょう。はじめに slice() 関数を使い、行番号をもとにmpgデータセットから一部を抽出し、それぞれmpg1, mpg2として保存します。

mpg1 <- mpg %>% slice(1) %>% 
  select(manufacturer,model,hwy,cty) %>%
  mutate(dataset = 1)

※ 翻訳記事では画像にしています
mpg2 <- mpg %>% slice(44:45) %>%
  select(manufacturer,model,hwy,cty) %>%
  mutate(dataset = 2)

※ 翻訳記事では画像にしています

この2つのデータセットは、同じ列から構成されているため、行方向に結合できます。bind_rows() 関数を使いましょう。

mpg_stack_vert <- mpg1 %>% 
  bind_rows(mpg2)

※ 翻訳記事では画像にしています

続いて、もうひとつデータセットを作成しましょう。上記のデータセットと抽出する行は同じですが、選択する列が異なります。これをmpg3として保存します。

mpg3 <- mpg %>% slice(1,44:45) %>%
  select(displ,year)

※ 翻訳記事では画像にしています

これらのデータセットを、列方向に結合しましょう (slice() 関数を用いて同じ行を抽出しているため、結合できます)。bind_cols() 関数を使います。

mpg_stack_horz <- mpg_stack_vert %>%
  bind_cols(mpg3)

※ 翻訳記事では画像にしています

データの結合 (Join)

もしあなたが、共通する1つまたは複数の「キー」があるデータセットを持っていれば、dplyrのjoin系関数を使い、データセットを結合できます。[4]まず、distinct() 関数を使い、"car_type" というデータセットを作ってみましょう。

訳注: distinct() 関数について何も説明がありませんが、データを重複なく一意に集約する関数です。

car_type <- mpg %>% select(manufacturer,model,class) %>%
  distinct()

※ 翻訳記事では画像にしています

そして、この "car_type" データセットを上で作成した "mpg_stack_horz" データセットに結合します。結合には left_join() 関数を使います。キーとして、"manufacturer" 列と "model" 列を使います。結果は、"joined" という名前で保存します。データセットには、"mpg_stack_horz" の全ての列と、"car_type" の "class" 列が含まれます。

joined <- mpg_stack_horz %>%
  left_join(car_type,by = c('manufacturer','model')) %>% 
  select(-dataset,everything()) # "dataset" 列を最後尾に移動する

※ 翻訳記事では画像にしています

ロング (縦持ち) からワイド (横持ち) フォーマットへの変換

tidyrパッケージに付属する、us_rent_incomeデータセットの内容を確認してみましょう。


※ 翻訳記事では画像にしています

このデータセットの各行には、年収 (中央値) または家賃 (月額の中央値) が記録されていて、どちらであるかは "variable" 列の値で判別できます。このようなデータ構造を "ロング"[5] フォーマットと呼びます。ロングフォーマットは、コンピュータでデータを操作 (加工) するのに適しています。一方で、データを視認・提示する際には "ワイド" フォーマットのほうが適しており、必要に応じて変換します。

ロングフォーマットをワイドフォーマットに変換するには、tidyr パッケージの pivot_wider() 関数を使います。変換した結果、家賃と年収はそれぞれ個別に列として記録されます。この関数は2つの引数を必要とします。

  • names_from: 新しく列として分割するカテゴリ名が含まれる列名を指定します。
  • values_from: 新しく列として分割する値が含まれる列名を指定します。

加えて、処理対象の列から欠損値を除去する drop_na() 関数も使います。また、年収に占める家賃 (12倍した年間換算) の比率を求めます。

col_ratio <- us_rent_income %>%
  select(-GEOID,-moe) %>%
  pivot_wider(names_from = variable, values_from = estimate) %>% 
  drop_na() %>%
  mutate(income_rent_ratio = income / (12*rent))

※ 翻訳記事では画像にしています

ワイド (横持ち) からロング (縦持ち) フォーマットへの変換

続いて、同じくtidyrパッケージに含まれるwold_bank_popデータセットを見てみましょう。


※ 翻訳記事では画像にしています

このデータセットは、カテゴリ変数 (例えば、各年) が列名として設定された、ワイドフォーマットになっています。[6]このデータセットを、コンピュータによる操作 (加工) に適したロングフォーマットに変換するには、pivot_longer() 関数を使います。この関数は3つの引数を必要とします。

  • cols: 第1引数に指定します。縦持ちに変換したい列のリストを指定します。この例では、縦持ちの場合のカテゴリとして指定したくない列を除外することで対処しています。
  • names_to: 縦持ちのために新しく作成する、カテゴリが含まれる列の名前を指定します。
  • values_to: 縦持ちのために新しく作成する、値が含まれる列の名前を指定します。

以下の例では、他に mutate() 関数と as.numeric() 関数を使い、新しく再生した year 列の型を数値型に変換しています。それにより、後続の filter() 関数と seq() 関数を組み合わせた記述で、指定した範囲の年のデータのみ抽出しています。seq(開始値, 修了値, 間隔) 関数は、指定した範囲の数値を生成します。

wb_pop <- world_bank_pop %>%
  pivot_longer(c(-country,-indicator), names_to = "year", values_to = "value") %>%
  mutate(year = as.numeric(year)) %>% # 数値型に変換
  filter(year %in% seq(2000,2016,2))

※ 翻訳記事では画像にしています

可視化

ここまで、データセットを加工し、集計、要約してきましたが、ここからは ggplot2パッケージを使ったいくつかの可視化手法を紹介します。ggplot2パッケージでは、さまざまな描画関数を + 演算子でつないでグラフィックスを作成します。グラフィックスをカスタマイズするための豊富な関数が用意されており、それらを結合してさまざまなグラフィックスを作成できます。

以下に、いくつかのよく使われるグラフを作成するコードを示しますが、さらに詳しい情報はggplot2のドキュメントで得られます。ここでは、概要の説明にとどめます。

  • ggplot() 関数は、描画対象のデータセットを指定し、グラフィックスを初期化するために使います。
  • geom_*() (Geometric) 関数は、データをどのように描画するかを指定します (geom_histogram(), geom_point(), geom_line() など)。
  • aes() (Aesthetic) 関数は、データセット内のどの変数を描画するかを指定します。この関数は、ggplot() 関数や geom_*() 関数の内部で使用し、どこに記述するかによって、影響する範囲を制御できます。
  • グラフィックスの見栄え (余白、凡例、目盛線など) は、theme_*() 関数で設定できます。また、theme() 関数を使い、自作のテーマを作成できます。
  • colorfill オプションを指定することで、線や点、(棒グラフの) 塗りつぶしの色をカスタマイズできます。これらのオプションには、navy など色名を直接指定したり、塗り分けの基準となるカテゴリ変数 (列) を指定します。詳細はggplot2のドキュメントを参照してください。
  • グラフィックスを保存するには ggsave() 関数を使います。

 散布図

散布図は、主に2つの連続変数間の関係を確認するために使われます。ggplot2では、geom_point() 関数で作成します。ここでは、mpgデータセットの中で、エンジンの排気量と高速道路における燃費の関係を見てみましょう。データセットの "trans" 列を str_detect() 関数で加工して新たに "Transmission" という列を作成し、"auto" または "manual" のカテゴリ変数を格納します。

aes() 関数の中で color オプションを指定します。これは、散布図の点を "Transmission" 列の値によって塗り分けるために使用します。凡例は自動的にグラフィックスの上部に作成されます。

ggplot(data = mpg %>% 
  mutate(Transmission = case_when(str_detect(trans,'auto') ~ 'auto',TRUE ~ 'manual')),
  aes(x = displ, y = hwy, color = Transmission)) +
geom_point() +
theme_light() +
theme(legend.position = 'top',
      legend.text = element_text(size = 11)) +
xlab('Displacement (L)') +
ylab('Highway MPG')

 折れ線グラフ

続いて、先ほど作成した "wb_pop" データセットのうち、"SP.POP.GROW" カテゴリの値が年ごとにどのように変化しているか、折れ線グラフで表現してみましょう。"SP.POP.GROW" カテゴリは、国ごとの人口増加率を表します。この値を100で割り、パーセンテージを小数表記にします。

この例では、geom_point() 関数と geom_line() 関数を使い、折れ線とデータ点を表示しています。expand_scale() 関数は、x軸の余白を調整するために使っています。また、y軸のラベルを設定するため、scale_y_continuous 関数を使っています。

ggplot(wb_pop %>% filter(country %in% c("USA","CAN","MEX") & indicator == "SP.POP.GROW"), 
       aes(x = year,y = value/100,color = country)) +
theme_minimal() + 
geom_line() + geom_point() + # lines and points
scale_x_continuous(expand = expand_scale(mult = c(.05, .05))) +
scale_y_continuous(labels = scales::percent) + 
theme(legend.title = element_blank(), # suppress legend title
      panel.grid.minor.x = element_blank(),
      legend.text = element_text(size = 11),
      legend.position = 'right') +
xlab('Year') + ylab('Population Growth')

 ヒストグラム

ヒストグラムは、変数の分布を表現するグラフです。ここでは、高速道路燃費の分布を見てみましょう。geom_histogram() 関数の binwidth オプションをさまざまに変更することで、グラフの見た目がどのように変化するか、確認してみてください。expand_scale() 関数をy軸の余白を調整するために使用しています。

ggplot(mpg,aes(hwy)) +
geom_histogram(binwidth = 1) +
theme_bw() +
scale_y_continuous(expand = expand_scale(mult = c(0, .05))) +
xlab('Highway MPG') + ylab('Vehicles')

 棒グラフ

棒グラフは、一般にカテゴリ間の相対的な大小関係を表すために使われます。ggplot2では geom_bar() 関数を使います。棒を度数・比率の大きさの順に並べるとわかりやすいため、reorder() 関数を使っています。geom_text() 関数を使い、棒の上にラベルを配置しています。

ggplot(data = mpg_stats,
    aes(x = reorder(class_c,-mean_hwy), y = mean_hwy)) +
geom_bar(stat = 'identity', color = 'black') +
scale_y_continuous(expand = expand_scale(mult = c(0, .1))) + # 上部の余白を調整する
geom_text(aes(label = round(mean_hwy)), vjust = -0.5) +  # 棒にラベルを付与する
theme_bw() + 
xlab('Vehicle Class') + ylab('Mean Highway MPG') + # 軸ラベルを指定する
theme(panel.grid = element_blank()) # グラフ領域の罫線を表示しない

 ロリポップチャート

ロリポップチャート[7]は、棒グラフに代わる魅力的なグラフです。ここでは、geom_segment() 関数と geom_point() 関数を使って作成します。また、coord_flip() 関数でグラフを横向きに変換しています。theme() 関数で独自のテーマを作成し、罫線の表示、非表示を指定しています。reorder() 関数は要素の並び順を変更します。この例では家賃を降順に並べ替えています。

ggplot(data = col_ratio %>% arrange(desc(rent)) %>% head(15), aes(x = NAME, y = rent) ) +
geom_segment( aes(x = reorder(NAME,rent) ,xend = NAME, y = 0, yend = rent), color = "grey") +
geom_point(size = 3) +
theme_minimal() +
theme(plot.subtitle = element_text(face = "bold",hjust = 0.5),
  plot.title = element_text(lineheight = 1, face = "bold",hjust = 0.5),
  panel.grid.minor.y = element_blank(),
  panel.grid.major.y = element_blank(),
  panel.grid.minor.x = element_blank()
) +
coord_flip() +
scale_y_continuous(labels = scales::dollar,expand = expand_scale(mult = c(0, .1))) + 
labs(title = 'US States with the Highest Rent',
  caption = 'Source: 2017 American Community Survey (Census)') +
xlab('') + ylab('Median Monthly Rent')

参考

ここまで述べたことに加え、いくつか有用だと思う情報を紹介します。

  • ファイルからデータを読み込むには、readr (CSV、テキストファイル) パッケージやreadxl (Excel形式) パッケージを使います。
  • データセット内の列について、データ型を変換するには、as.numeric() 関数、as.character() 関数、as.Date() 関数、as.factor() 関数などを使います。これらは、Rの組み込み関数です。日付・時刻のデータについてはlubridateパッケージで、文字列についてはstringrパッケージで、factor型についてはforcatsパッケージでそれぞれ、さらに詳細な操作ができます。
  • データを簡潔に要約するには、組み込み関数 summary() や、skimrパッケージを使います。
  • purrrパッケージは、map_*() 系の、データに対して並列・繰り返し処理を適用する仕組みを提供します。例えば、Excelファイルの複数のシートに対して、データの読み込みと処理を繰り返し行うには、1つのシートに対して処理を行う関数を map_*() 関数を適用します。
  • 私 (著者) は、継続的にRやPythonによるデータサイエンス関連のコードをGitHubレポジトリで公開しています。そこでモデリングなど、より発展的なテクニックについて知ることができるでしょう。

  • [1]訳注: この記事では、tidyverseとは何か、ということは説明されていません。以下の「関連ページ」にも記載している「JapanR2019_初心者セッション_今日からはじめるR」などを参照してください。
  • [2]訳注: 処理した結果を既存の列に上書きすることもできます。
  • [3]訳注: dplyrパッケージでは、summarise() と summarize() の2つの関数が用意されており、機能は同じです。イギリス英語とアメリカ英語の違いらしいです。
  • [4]訳注: もはや「Joinとは何か」とか一切ないですね。
  • [5]訳注: 日本語では「縦持ち」とよく言われます。
  • [6]訳注: このような形式は、集計表として人間が見るぶんには便利ですが、コンピュータによる操作 (加工) のしやすさ = 機械可読性という面では扱い辛い構造です。
  • [7]訳注: 〇〇図といった日本語の名前はあるんでしょうか?

関連ページ: JapanR2019_初心者セッション_今日からはじめるR, R言語を学ぶための参考書籍リスト
カテゴリ: [R,データサイエンス,データ分析]