第9回講義資料

データハンドリング (1)

スライド

新しいタブで開く

Tidyverseとパイプ演算子

Tidyverseとは

 Tidyverse(たいでぃばーす)とはデータサイエンスのために考案された、強い信念と思想に基づいたRパッケージの集合である。具体的には{dplyr}、{tidyr}、{readr}、{ggplot2}など数十パッケージで構成されており、Tidyverseに属するパッケージは思想、文法およびデータ構造(tidy data; 整然データ)を共有しる。また、ベクトルやデータフレーム、関数などのオブジェクトをパイプ演算子 (|>)で繋ぐという点が特徴である。これらのパッケージ群は{tidyverse}をインストールすることで導入可能であり、JDCat分析ツールでは既に導入済みである。

 2010年代中盤以降、Rの書き方はtidyverseな書き方が定着しているため、Rのコードを作成する際はlibrary(tidyverse)から始めよう。

パイプ演算子

 Tidyverseにおいてオブジェクトはパイプ演算子(|>)で繋がっている。なぜパイプ演算子を使うのだろうか。一般的なプログラミング言語に共通する書き方は書き方と読み方が逆という特徴を持つ。たとえば、「ベクトルXの和を出力する」コードはprint(sum(X))であり、printsumXの順で書く。このコードの読み方には二通りがある。

  1. 読み方1: Xsum()し、print()する。
  2. 読み方2: print()する内容はsum()で、sum()Xに対して行う。

 1は人間にとって自然な読み方であるが、書き方と逆である。一方、2は書き方と順番は一致するものの、直感的ではない。しかし、Tidyverseな書き方、つまりパイプ演算子を用いた書き方は書き方と読み方が一致するメリットがある。たとえば、先ほどのコードはパイプ演算子を使うとX |> sum() |> print()と書く。

 このパイプ演算子、実はごく簡単な仕組みである。|>の左側のオブジェクトを右側オブジェクトの最初の引数として渡すだけであり、X |> 関数(Y)関数(X, Y)と同じだ。つまり、X |> sum(na.rm = TRUE)sum(X, na.rm = TRUE)と同じコードである。

既存の書き方

Code 01-a
X <- c(2, 3, 5, NA, 11)
print(sum(X, na.rm = TRUE))
[1] 21

Tidyverseな書き方

Code 01-b
library(tidyverse)
X |> sum(na.rm = TRUE) |> print()
[1] 21

 |>演算子はパイプ左側オブジェクトを右側オブジェクトの最初の引数として渡すと説明したたが、第2、3、…引数として渡すことも可能である。ただし、この場合はパイプ左側のオブジェクトが入る箇所に_と記入する必要がある。詳細は割愛するが、線形回帰分析の関数lm()は最初の引数が数式(formula型)であるため、データフレームをlm()に渡すためには、lm()内にdata = _と書く必要がある。これは_の箇所にパイプ左側のオブジェクトが入ることを意味する。

Code 02
# 以下のコードは lm(y ~ x1 + x2 + x3, data = my_data) と同じ
my_data |> 
  lm(y ~ x1 + x2 + x3, data = _)

 このパイプ演算子(|>)、実は2021年5月リリースされたR 4.1から使用可能な比較的新しいものである。しかし、パイプ演算子そのものの歴史は短くはない。R 4.1がリリースされるまではRにパイプ演算子は存在しなかったものの、{magrittr}というパッケージが独自のパイプ演算子(%>%)を提供してきた。%>%パイプ演算子の使い方は|>と同じであるため、現段階では何を使っても問題ない。しかし、R界隈の神様であるHadley Wickham先生も現在は%>%から|>へ乗り換え済みであることから、これからの主流は|>になっていくと予想される。現時点に存在する教科書、インターネット記事では%>%が主流になっているが、それらのコードのパイプ演算子は基本的に|>に置換しても問題ない。ただし、第一引数以外に使う位置指定子(place holder)が異なる点に注意しておこう。%>%の位置指定子は.であり、|>の位置指定子は_である。本講義ではパイプ演算子として|>を使用するが、|>を使っても同じ結果が得られる。少なくとも本講義に限定した場合%>%|>は同じものであると考えても良い。

|>が打ちにくい…!!

 旧パイプ演算子%>%に比べ、|>は打ちにくいかも知れない。しかし、RStudioを使うのであればショートカットを活用しよう。macOSなら「⌘ + Shift + m」、Windows/Linuxなら「Control (Ctrl) + Shift + m」を同時に押すと、自動的に|>が入力される(RStudioの設定によっては|>でなく、%>%が入力される)。

{dplyr}とは

 {dplyr}はTidyverseパッケージ群のコア・パッケージの一つである、表形式データ (データフレームやtibble)を操作するパッケージである。{dplyr}を使えば、第8回の講義で解説した行・列の抽出も簡単に可能となる。{dplyr}を使うためには別途library(dplyr)を実行する必要はなく、{tidyverse}を読み込む際に自動的に読み込まれる。

 まずは本講義に使用するデータを読み込んでみよう。サポートページからcountries.csvをダウンロードし、プロジェクト・フォルダーにアップロードする。宋はプロジェクト・フォルダー内にDataという名のフォルダーを作成し、そこにデータをアップロードした。

Code 03
library(tidyverse) # {tidyverse}の読み込み
# Dataフォルダー内のcountries.csvを読み込み、dfという名のオブジェクトとして作業環境に格納
df <- read_csv("Data/countries.csv")

 読み込んだデータを出力してみよう。

Code 04
df
# A tibble: 186 × 18
   Country  Population   Area    GDP     PPP GDP_per_capita PPP_per_capita    G7
   <chr>         <dbl>  <dbl>  <dbl>   <dbl>          <dbl>          <dbl> <dbl>
 1 Afghani…   38928346 6.53e5 1.91e4  8.27e4           491.          2125.     0
 2 Albania     2877797 2.74e4 1.53e4  3.97e4          5309.         13781.     0
 3 Algeria    43851044 2.38e6 1.70e5  4.97e5          3876.         11324.     0
 4 Andorra       77265 4.7 e2 3.15e3 NA              40821.            NA      0
 5 Angola     32866272 1.25e6 9.46e4  2.19e5          2879.          6649.     0
 6 Antigua…      97929 4.4 e2 1.73e3  2.08e3         17643.         21267.     0
 7 Argenti…   45195774 2.74e6 4.50e5  1.04e6          9949.         22938.     0
 8 Armenia     2963243 2.85e4 1.37e4  3.84e4          4614.         12974.     0
 9 Austral…   25499884 7.68e6 1.39e6  1.28e6         54615.         50001.     0
10 Austria     9006398 8.24e4 4.46e5  5.03e5         49555.         55824.     0
# ℹ 176 more rows
# ℹ 10 more variables: G20 <dbl>, OECD <dbl>, HDI_2018 <dbl>,
#   Polity_Score <dbl>, Polity_Type <chr>, FH_PR <dbl>, FH_CL <dbl>,
#   FH_Total <dbl>, FH_Status <chr>, Continent <chr>

 dim()関数を使うとdfは186行、18列のデータであることが分かる。

Code 05
dim(df)
[1] 186  18

 また、18個の変数名のみを出力するためにはnames()関数を使う。

Code 06
names(df)
 [1] "Country"        "Population"     "Area"           "GDP"           
 [5] "PPP"            "GDP_per_capita" "PPP_per_capita" "G7"            
 [9] "G20"            "OECD"           "HDI_2018"       "Polity_Score"  
[13] "Polity_Type"    "FH_PR"          "FH_CL"          "FH_Total"      
[17] "FH_Status"      "Continent"     

 この実習用データは186カ国の社会経済・政治体制のデータであり、18個の変数で構成されている。詳細は以下の通りである。

変数名 説明 変数名 説明
Country 国名 OECD OECD加盟有無
Population 人口 HDI_2018 人間開発指数 (2018年)
Area 面積( \(\text{km}^2\) ) Polity_Score 政治体制のスコア
GDP 国内総生産(ドル) Polity_Type 政治体制
PPP 購買力平価国内総生産 FH_PR 政治的自由
GDP_per_capita 一人当たりGDP FH_CL 市民的自由
PPP_per_capita 一人当たりPPP FH_Total FH_PR + FH_CL
G7 G7加盟有無 FH_Status 自由の状態
G20 G20加盟有無 Continent 大陸

列の抽出

 データフレームから特定の(column)を抽出する際はselect()関数を使用する。select()関数の第一引数はデータフレームのオブジェクト名であり、第二引数以降では抽出する列名を記入する。第一引数がデータフレームであるため、パイプ演算子を使うことが可能である。

パイプを使わない書き方

Code 07-a
select(データ, 変数名1, 変数名2, ...)

パイプを使う書き方

Code 07-b
データ |>
  select(変数名1, 変数名2, ...)
注意: select()関数は複数ある!

select()関数は{dplyr}だけでなく、{MASS}からも提供されるが、別の関数である。

  • {MASS}もデータ分析において頻繁に使われるパッケージであるため、select()だけだと、どのパッケージのselect()か分からなくなる場合がある。
  • エラーが生じる場合は、dplyr::select()など、パッケージ名を指定すること

 それではいくつかの例を紹介しよう。まずはdfからCountryPopulationHDI_2018列を抽出してみよう。

Code 08
df |>                                   # dfから
  select(Country, Population, HDI_2018) # Country, Population, HDI_2018のみ抽出する
# A tibble: 186 × 3
   Country             Population HDI_2018
   <chr>                    <dbl>    <dbl>
 1 Afghanistan           38928346    0.496
 2 Albania                2877797    0.791
 3 Algeria               43851044    0.759
 4 Andorra                  77265    0.857
 5 Angola                32866272    0.574
 6 Antigua and Barbuda      97929    0.776
 7 Argentina             45195774    0.83 
 8 Armenia                2963243    0.76 
 9 Australia             25499884    0.938
10 Austria                9006398    0.914
# ℹ 176 more rows

 この操作を第8回で紹介した方法で行う場合、以下のようなコードとなる。どちらの方が可読性が良いかは一目瞭然だろう。

既存の書き方

Code 09-a
df[, c("Country", "Population", "HDI_2018")]
# A tibble: 186 × 3
   Country             Population HDI_2018
   <chr>                    <dbl>    <dbl>
 1 Afghanistan           38928346    0.496
 2 Albania                2877797    0.791
 3 Algeria               43851044    0.759
 4 Andorra                  77265    0.857
 5 Angola                32866272    0.574
 6 Antigua and Barbuda      97929    0.776
 7 Argentina             45195774    0.83 
 8 Armenia                2963243    0.76 
 9 Australia             25499884    0.938
10 Austria                9006398    0.914
# ℹ 176 more rows

Tidyverseな書き方

Code 09-b
df |>
  select(Country, Population, HDI_2018)
# A tibble: 186 × 3
   Country             Population HDI_2018
   <chr>                    <dbl>    <dbl>
 1 Afghanistan           38928346    0.496
 2 Albania                2877797    0.791
 3 Algeria               43851044    0.759
 4 Andorra                  77265    0.857
 5 Angola                32866272    0.574
 6 Antigua and Barbuda      97929    0.776
 7 Argentina             45195774    0.83 
 8 Armenia                2963243    0.76 
 9 Australia             25499884    0.938
10 Austria                9006398    0.914
# ℹ 176 more rows

 注意すべき点として、この時点では抽出・出力されただけだということだ。抽出したデータを引き続き使うためには、代入演算子(<-)を使って、抽出した結果を別途のオブジェクトとして格納する必要がある1。たとえば、dfから3つの変数を抽出したものをdf2という名で作業環境内に格納するためには以下のように入力する。

Code 10
df2 <- df |>
  select(Country, Population, HDI_2018)

 ls()で現在の作業環境内のオブジェクトの一覧を出力するとdf2がある。

Code 11
ls()
[1] "df"              "df2"             "has_annotations" "X"              

 df2を出力してみるとdfから3つの列のみ抽出されたものがdf2という新しい名のオブジェクトとして格納されていることが分かる。

Code 12
df2
# A tibble: 186 × 3
   Country             Population HDI_2018
   <chr>                    <dbl>    <dbl>
 1 Afghanistan           38928346    0.496
 2 Albania                2877797    0.791
 3 Algeria               43851044    0.759
 4 Andorra                  77265    0.857
 5 Angola                32866272    0.574
 6 Antigua and Barbuda      97929    0.776
 7 Argentina             45195774    0.83 
 8 Armenia                2963243    0.76 
 9 Australia             25499884    0.938
10 Austria                9006398    0.914
# ℹ 176 more rows
課題では格納が必要な場合もある

課題の問題には「出力せよ」だけでなく、「格納した上で出力せよ」といった形式もある。加工したデータを引き続き使うためには格納が必須であるため、問題文を注意深く読むこと。

 select()関数のもう一つの特徴は、変数名の変更と抽出を同時に行えることだ。抽出する際、新しい変数名 = 既存の変数名と記入すれば、抽出と同時に変数名も変更できる。たとえば、dfからCountryPopulationHDI_2018を抽出し、HDI_2018の変数名をHDIに変更したい場合は以下のように書く。

Code 13
df |>
  select(Country, Population, HDI = HDI_2018)
# A tibble: 186 × 3
   Country             Population   HDI
   <chr>                    <dbl> <dbl>
 1 Afghanistan           38928346 0.496
 2 Albania                2877797 0.791
 3 Algeria               43851044 0.759
 4 Andorra                  77265 0.857
 5 Angola                32866272 0.574
 6 Antigua and Barbuda      97929 0.776
 7 Argentina             45195774 0.83 
 8 Armenia                2963243 0.76 
 9 Australia             25499884 0.938
10 Austria                9006398 0.914
# ℹ 176 more rows

 抽出は行わず、変数名のみを変更したい場合はrename()関数を使用する。使い方はselect()関数と同じである。たとえば、dfPopulationJinkoに、AreaMensekiに変更し、18列は温存する場合は以下のように書く。

Code 14
df |>
  rename(Jinko = Population, Menseki = Area)
# A tibble: 186 × 18
   Country      Jinko Menseki    GDP     PPP GDP_per_capita PPP_per_capita    G7
   <chr>        <dbl>   <dbl>  <dbl>   <dbl>          <dbl>          <dbl> <dbl>
 1 Afghanistan 3.89e7  652860 1.91e4  8.27e4           491.          2125.     0
 2 Albania     2.88e6   27400 1.53e4  3.97e4          5309.         13781.     0
 3 Algeria     4.39e7 2381740 1.70e5  4.97e5          3876.         11324.     0
 4 Andorra     7.73e4     470 3.15e3 NA              40821.            NA      0
 5 Angola      3.29e7 1246700 9.46e4  2.19e5          2879.          6649.     0
 6 Antigua an… 9.79e4     440 1.73e3  2.08e3         17643.         21267.     0
 7 Argentina   4.52e7 2736690 4.50e5  1.04e6          9949.         22938.     0
 8 Armenia     2.96e6   28470 1.37e4  3.84e4          4614.         12974.     0
 9 Australia   2.55e7 7682300 1.39e6  1.28e6         54615.         50001.     0
10 Austria     9.01e6   82409 4.46e5  5.03e5         49555.         55824.     0
# ℹ 176 more rows
# ℹ 10 more variables: G20 <dbl>, OECD <dbl>, HDI_2018 <dbl>,
#   Polity_Score <dbl>, Polity_Type <chr>, FH_PR <dbl>, FH_CL <dbl>,
#   FH_Total <dbl>, FH_Status <chr>, Continent <chr>

 18の変数の中から17個を抽出したい場合はselect()内に17つの変数名を入れれば良いが、これは非効率的である。select()関数は変数名の前に! (推奨)、または-を付けることで、特定の列を除外することができる。また、2つ以上の変数を除外する場合、変数名をc()や、後述する:でまとめることもできる。。

 たとえば、dfからPopulationAreaGDPPPP列を除外するコードは以下の通りである。

Code 15
df |>
  select(!c(Population, Area, GDP, PPP))
# A tibble: 186 × 14
   Country GDP_per_capita PPP_per_capita    G7   G20  OECD HDI_2018 Polity_Score
   <chr>            <dbl>          <dbl> <dbl> <dbl> <dbl>    <dbl>        <dbl>
 1 Afghan…           491.          2125.     0     0     0    0.496           -1
 2 Albania          5309.         13781.     0     0     0    0.791            9
 3 Algeria          3876.         11324.     0     0     0    0.759            2
 4 Andorra         40821.            NA      0     0     0    0.857           NA
 5 Angola           2879.          6649.     0     0     0    0.574           -2
 6 Antigu…         17643.         21267.     0     0     0    0.776           NA
 7 Argent…          9949.         22938.     0     1     0    0.83             9
 8 Armenia          4614.         12974.     0     0     0    0.76             7
 9 Austra…         54615.         50001.     0     1     1    0.938           10
10 Austria         49555.         55824.     0     0     1    0.914           10
# ℹ 176 more rows
# ℹ 6 more variables: Polity_Type <chr>, FH_PR <dbl>, FH_CL <dbl>,
#   FH_Total <dbl>, FH_Status <chr>, Continent <chr>

 以上の例だと、PopulationからPPPは連続して位置する変数であるが、{dplyr}では:演算子を使うと、XXからYYまでといった選択ができる。たとえば、「PopulationからPPPまで」は「Population:PPP」と表記する。つまり、上記のコードは以下のように書くこともできる。

Code 16
df |>
  select(!c(Population:PPP))
# A tibble: 186 × 14
   Country GDP_per_capita PPP_per_capita    G7   G20  OECD HDI_2018 Polity_Score
   <chr>            <dbl>          <dbl> <dbl> <dbl> <dbl>    <dbl>        <dbl>
 1 Afghan…           491.          2125.     0     0     0    0.496           -1
 2 Albania          5309.         13781.     0     0     0    0.791            9
 3 Algeria          3876.         11324.     0     0     0    0.759            2
 4 Andorra         40821.            NA      0     0     0    0.857           NA
 5 Angola           2879.          6649.     0     0     0    0.574           -2
 6 Antigu…         17643.         21267.     0     0     0    0.776           NA
 7 Argent…          9949.         22938.     0     1     0    0.83             9
 8 Armenia          4614.         12974.     0     0     0    0.76             7
 9 Austra…         54615.         50001.     0     1     1    0.938           10
10 Austria         49555.         55824.     0     0     1    0.914           10
# ℹ 176 more rows
# ℹ 6 more variables: Polity_Type <chr>, FH_PR <dbl>, FH_CL <dbl>,
#   FH_Total <dbl>, FH_Status <chr>, Continent <chr>

 むろん、:は否定演算子じゃなくても使える。dfCountryPPP, HDI_2018列を抽出するコードは以下の通りである。

Code 17
df |>
  select(Country:PPP, HDI_2018)
# A tibble: 186 × 6
   Country             Population    Area      GDP      PPP HDI_2018
   <chr>                    <dbl>   <dbl>    <dbl>    <dbl>    <dbl>
 1 Afghanistan           38928346  652860   19101.   82737.    0.496
 2 Albania                2877797   27400   15278.   39658.    0.791
 3 Algeria               43851044 2381740  169988.  496572.    0.759
 4 Andorra                  77265     470    3154.      NA     0.857
 5 Angola                32866272 1246700   94635.  218533.    0.574
 6 Antigua and Barbuda      97929     440    1728.    2083.    0.776
 7 Argentina             45195774 2736690  449663. 1036721.    0.83 
 8 Armenia                2963243   28470   13673.   38446.    0.76 
 9 Australia             25499884 7682300 1392681. 1275027.    0.938
10 Austria                9006398   82409  446315.  502771.    0.914
# ℹ 176 more rows

 以下では変数を選択するより洗練された方法について解説する。変数名が特定の文字列で始まる列を選択する関数としてstarts_with()がある。たとえば、dfからCountry, "FH"で始まる列を抽出する場合は以下のように書く。

Code 18
df |>
  select(Country, starts_with("FH"))
# A tibble: 186 × 5
   Country             FH_PR FH_CL FH_Total FH_Status
   <chr>               <dbl> <dbl>    <dbl> <chr>    
 1 Afghanistan            13    14       27 NF       
 2 Albania                27    40       67 PF       
 3 Algeria                10    24       34 NF       
 4 Andorra                39    55       94 F        
 5 Angola                 11    21       32 NF       
 6 Antigua and Barbuda    33    52       85 F        
 7 Argentina              35    50       85 F        
 8 Armenia                21    32       53 PF       
 9 Australia              40    57       97 F        
10 Austria                37    56       93 F        
# ℹ 176 more rows

 starts_with()の前に!を付けると該当する列が除外される。たとえば、"GDP""PPP"で始まる列を除外する場合はstarts_with()内にcharacter型ベクトルを入れる。

Code 19
df |>
  select(!starts_with(c("GDP", "PPP")))
# A tibble: 186 × 14
   Country Population   Area    G7   G20  OECD HDI_2018 Polity_Score Polity_Type
   <chr>        <dbl>  <dbl> <dbl> <dbl> <dbl>    <dbl>        <dbl> <chr>      
 1 Afghan…   38928346 6.53e5     0     0     0    0.496           -1 Closed Ano…
 2 Albania    2877797 2.74e4     0     0     0    0.791            9 Democracy  
 3 Algeria   43851044 2.38e6     0     0     0    0.759            2 Open Anocr…
 4 Andorra      77265 4.7 e2     0     0     0    0.857           NA <NA>       
 5 Angola    32866272 1.25e6     0     0     0    0.574           -2 Closed Ano…
 6 Antigu…      97929 4.4 e2     0     0     0    0.776           NA <NA>       
 7 Argent…   45195774 2.74e6     0     1     0    0.83             9 Democracy  
 8 Armenia    2963243 2.85e4     0     0     0    0.76             7 Democracy  
 9 Austra…   25499884 7.68e6     0     1     1    0.938           10 Full Democ…
10 Austria    9006398 8.24e4     0     0     1    0.914           10 Full Democ…
# ℹ 176 more rows
# ℹ 5 more variables: FH_PR <dbl>, FH_CL <dbl>, FH_Total <dbl>,
#   FH_Status <chr>, Continent <chr>

 starts_with()に似たような機能をする関数として特定の文字列で終わる列を選択するends_with()と特定の文字列を含む列を選択するcontains()がある。これらの使い方はstarts_with()と同じだ。

 続いて、列の順番を変更する方法を紹介する。実はselect()関数は書かれた順番で列を抽出する。たとえば、G7からOECD列をCountryPopulationの間へ移動する場合は以下のように書く。

Code 20
df |>
  select(Country, G7:OECD,
         Population:PPP_per_capita, HDI_2018:Continent)
# A tibble: 186 × 18
   Country        G7   G20  OECD Population   Area    GDP     PPP GDP_per_capita
   <chr>       <dbl> <dbl> <dbl>      <dbl>  <dbl>  <dbl>   <dbl>          <dbl>
 1 Afghanistan     0     0     0   38928346 6.53e5 1.91e4  8.27e4           491.
 2 Albania         0     0     0    2877797 2.74e4 1.53e4  3.97e4          5309.
 3 Algeria         0     0     0   43851044 2.38e6 1.70e5  4.97e5          3876.
 4 Andorra         0     0     0      77265 4.7 e2 3.15e3 NA              40821.
 5 Angola          0     0     0   32866272 1.25e6 9.46e4  2.19e5          2879.
 6 Antigua an…     0     0     0      97929 4.4 e2 1.73e3  2.08e3         17643.
 7 Argentina       0     1     0   45195774 2.74e6 4.50e5  1.04e6          9949.
 8 Armenia         0     0     0    2963243 2.85e4 1.37e4  3.84e4          4614.
 9 Australia       0     1     1   25499884 7.68e6 1.39e6  1.28e6         54615.
10 Austria         0     0     1    9006398 8.24e4 4.46e5  5.03e5         49555.
# ℹ 176 more rows
# ℹ 9 more variables: PPP_per_capita <dbl>, HDI_2018 <dbl>, Polity_Score <dbl>,
#   Polity_Type <chr>, FH_PR <dbl>, FH_CL <dbl>, FH_Total <dbl>,
#   FH_Status <chr>, Continent <chr>

 しかし、いくら:演算子があるとはいえ、全ての変数名を書く必要がある。抽出と同時に順番を変更するならselect()が便利だが、順番のみを変更するならrelocate()関数がおすすめである。relocate()関数は移動する変数を指定し、.afterまたは.beforeで移動先を指定する必要がある。

Code 21
# 特定の変数の後ろへ移動
データ |>
  relocate(移動したい変数名, .after = 変更先)
# 特定の変数の前へ移動
データ |>
  relocate(移動したい変数名, .before = 変更先)

 たとえば、G7からOECD列をCountryの後ろへ移動させる場合は以下のように書く。

Code 22
df |>
  relocate(G7:OECD, .after = Country) # .before = PopulationもOK
# A tibble: 186 × 18
   Country        G7   G20  OECD Population   Area    GDP     PPP GDP_per_capita
   <chr>       <dbl> <dbl> <dbl>      <dbl>  <dbl>  <dbl>   <dbl>          <dbl>
 1 Afghanistan     0     0     0   38928346 6.53e5 1.91e4  8.27e4           491.
 2 Albania         0     0     0    2877797 2.74e4 1.53e4  3.97e4          5309.
 3 Algeria         0     0     0   43851044 2.38e6 1.70e5  4.97e5          3876.
 4 Andorra         0     0     0      77265 4.7 e2 3.15e3 NA              40821.
 5 Angola          0     0     0   32866272 1.25e6 9.46e4  2.19e5          2879.
 6 Antigua an…     0     0     0      97929 4.4 e2 1.73e3  2.08e3         17643.
 7 Argentina       0     1     0   45195774 2.74e6 4.50e5  1.04e6          9949.
 8 Armenia         0     0     0    2963243 2.85e4 1.37e4  3.84e4          4614.
 9 Australia       0     1     1   25499884 7.68e6 1.39e6  1.28e6         54615.
10 Austria         0     0     1    9006398 8.24e4 4.46e5  5.03e5         49555.
# ℹ 176 more rows
# ℹ 9 more variables: PPP_per_capita <dbl>, HDI_2018 <dbl>, Polity_Score <dbl>,
#   Polity_Type <chr>, FH_PR <dbl>, FH_CL <dbl>, FH_Total <dbl>,
#   FH_Status <chr>, Continent <chr>

行の抽出

 の抽出はselect()関数を使うが、の抽出にはfilter()関数を使う。行の抽出は基本的に、指定された条件に合致する行を抽出することを意味する。したがって、filter()関数を使うためには論理演算子(==>&など)の理解が必須だ。

 filter()関数の使い方は以下の通りである。パイプを使わずデータオブジェクト名をfilter()の第一引数として使っても良い。

Code 23
データ |>
  filter(条件1, 条件2, ...)

 たとえば、dfからContinentの値が"Europe"である行を抽出し、CountryPPP, HDI_2018列を抽出し、HDI_2018HDIに変更するコードは以下の通りである。行の抽出と列の抽出を同時に行っているため、filter()関数とselect()関数を組み合わせる必要がある。

Code 24
df |>
  filter(Continent == "Oceania") |>
  select(Country:PPP, HDI = HDI_2018)
# A tibble: 4 × 6
  Country          Population    Area      GDP      PPP   HDI
  <chr>                 <dbl>   <dbl>    <dbl>    <dbl> <dbl>
1 Australia          25499884 7682300 1392681. 1275027. 0.938
2 Fiji                 896445   18270    5536.   12496. 0.724
3 New Zealand         4842780  263820  206929.  204260. 0.921
4 Papua New Guinea    8947024  452860   24970.   37319. 0.543

 ただし、filter()select()の順番に注意しよう。filter()が3行目、select()が2行目に位置する場合、以下のコードは走らない。なぜなら、select()関数を通すことでContinent列が除外されるからだ。

 filter()内の条件式は二つ以上でも使える。たとえば、二つの条件を同時に満たす行を抽出するとしよう。この場合はAND演算子(&)を使う。たとえば、dfからContinent"Asia" (条件1)、HDI_2018が0.8以上 (条件2)の行を抽出し、CountryHDI_2018列を抽出する場合、以下のように書く。

Code 25
df |>
  filter(Continent == "Asia" & HDI_2018 >= 0.8) |>
  select(Country, HDI_2018)
# A tibble: 13 × 2
   Country              HDI_2018
   <chr>                   <dbl>
 1 Bahrain                 0.838
 2 Brunei                  0.845
 3 Israel                  0.906
 4 Japan                   0.915
 5 Kazakhstan              0.817
 6 South Korea             0.906
 7 Kuwait                  0.808
 8 Malaysia                0.804
 9 Oman                    0.834
10 Qatar                   0.848
11 Saudi Arabia            0.857
12 Singapore               0.935
13 United Arab Emirates    0.866

 二つの条件式を&で繋いだことに注意されたい。filter()関数で複数の条件式を使う場合は,&|のいずれかを使うが、,&は同じ(=AND演算子)である。混乱を避けるために&の使用を推奨するが、,を使っても良い。

 もう一つの演算子はOR演算子(|)である。これは2つ以上の条件のうち、一つ以上の条件を満たす行を抽出することを意味する。たとえば、dfからContinent"Asia"(条件1)か"Oceania"(条件2)の行を抽出し、CountryHDI_2018Continent列を抽出する場合、以下のように書く。

Code 26
df |>
  filter(Continent == "Asia" | Continent == "Oceania") |>
  select(Country, HDI_2018, Continent)
# A tibble: 46 × 3
   Country     HDI_2018 Continent
   <chr>          <dbl> <chr>    
 1 Afghanistan    0.496 Asia     
 2 Australia      0.938 Oceania  
 3 Bahrain        0.838 Asia     
 4 Bangladesh     0.614 Asia     
 5 Bhutan         0.617 Asia     
 6 Brunei         0.845 Asia     
 7 Burma          0.584 Asia     
 8 Cambodia       0.581 Asia     
 9 China          0.758 Asia     
10 Fiji           0.724 Oceania  
# ℹ 36 more rows

 AND演算子とOR演算子は同時に使うこともできる。たとえば、dfからContinent"Asia"(条件1)か"Oceania"(条件2)でありながら、HDI_2018が0.9以上 (条件3)の行を抽出し、CountryHDI_2018Continent列を抽出する場合は以下のようになる。ただし、AND演算子とOR演算子を同時に使用する場合は適切な箇所にカッコ(())を付ける必要がある。

Code 27
df |>
  filter((Continent == "Asia" | Continent == "Oceania"), 
         HDI_2018 >= 0.9) |>
  select(Country, HDI_2018, Continent)
# A tibble: 6 × 3
  Country     HDI_2018 Continent
  <chr>          <dbl> <chr>    
1 Australia      0.938 Oceania  
2 Israel         0.906 Asia     
3 Japan          0.915 Asia     
4 South Korea    0.906 Asia     
5 New Zealand    0.921 Oceania  
6 Singapore      0.935 Asia     

 実は上記のコード、もう少し効率化することもできる。そのためには%in%演算子を使う必要があるが、==を用いる複数の条件式がOR演算子で繋がっている場合、大変便利な演算子である。たとえば、Continentの値がc("Asia", "Oceainia")のいずれかに該当するかどうかを判定する場合は、これまで(Continent == "Asia" | Continent == "Oceania")と書いたが、これはContinent %in% c("Asia", "Oceania")と同じコードである。したがって、上記のコードは以下のように簡素化することもできる。

Code 28
df |>
  filter(Continent %in% c("Asia", "Oceania"), 
         HDI_2018 >= 0.9) |>
  select(Country, HDI_2018, Continent)
# A tibble: 6 × 3
  Country     HDI_2018 Continent
  <chr>          <dbl> <chr>    
1 Australia      0.938 Oceania  
2 Israel         0.906 Asia     
3 Japan          0.915 Asia     
4 South Korea    0.906 Asia     
5 New Zealand    0.921 Oceania  
6 Singapore      0.935 Asia     

欠損値の扱い

 データには欠損値が含まれていることが多く、Rでは欠損値をNAと表記する。欠損値が含まれている場合、多重代入法(multiple imputation)などの処理を施すことも可能だが、これはかなり高度の知識を要する。多重代入法を使わない場合は、欠損値を含むケースを除外することが有効である。なぜなら欠損値を含まれている場合、関数が使えないケースも多いからだ2。たとえば、一人あたり購買力平価GDP(PPP_per_capita)には欠損値が含まれている。この場合、平均値を計算するmean()関数はそのまま使えない。

Code 29
mean(df$PPP_per_capita)
[1] NA

 この場合、mean()関数内にna.rm = TRUEを付けて欠損していない値の平均値を求めるように指定するか、予め欠損値が含まれているケースを除外する必要がある。ここでは後者について説明する。

 まず、dfPPPが欠損している行を抽出し、CountryからPPP列まで出力してみよう。PPPが欠損している場合はNAの値を取るため、filter()内にPPP == NAと条件式を書けば動くかも知れない。

Code 30
df |>
  filter(PPP == NA) |>
  select(Country:PPP)
# A tibble: 0 × 5
# ℹ 5 variables: Country <chr>, Population <dbl>, Area <dbl>, GDP <dbl>,
#   PPP <dbl>

 いや、そうでもなかった。実はある値がNAか否かを判定するために==演算子は使えない。代わりにis.na()関数を使用する。

Code 31
df |>
  filter(is.na(PPP)) |>
  select(Country:PPP)
# A tibble: 8 × 5
  Country        Population   Area     GDP   PPP
  <chr>               <dbl>  <dbl>   <dbl> <dbl>
1 Andorra             77265    470   3154.    NA
2 Cuba             11326616 106440 100023     NA
3 Holy See              801      0     NA     NA
4 Liechtenstein       38128    160   6553.    NA
5 Monaco              39242      1   7188.    NA
6 Somalia          15893222 627340    917.    NA
7 Syria            17500658 183630  40405.    NA
8 Western Sahara     597339 266000    909.    NA

 しかし、我々が本当にやりたいことはPPPが欠損している行のみ抽出するのではなく、 PPPが欠損している行を除外することだ。ここでも否定を意味する!が使える。

Code 32
df |>
  filter(!is.na(PPP)) |>
  select(Country:PPP)
# A tibble: 178 × 5
   Country             Population    Area      GDP      PPP
   <chr>                    <dbl>   <dbl>    <dbl>    <dbl>
 1 Afghanistan           38928346  652860   19101.   82737.
 2 Albania                2877797   27400   15278.   39658.
 3 Algeria               43851044 2381740  169988.  496572.
 4 Angola                32866272 1246700   94635.  218533.
 5 Antigua and Barbuda      97929     440    1728.    2083.
 6 Argentina             45195774 2736690  449663. 1036721.
 7 Armenia                2963243   28470   13673.   38446.
 8 Australia             25499884 7682300 1392681. 1275027.
 9 Austria                9006398   82409  446315.  502771.
10 Azerbaijan            10139177   82658   48048.  144556.
# ℹ 168 more rows

 このようにPPPが欠損していない行だけが抽出された。また、より簡単な方法としてdrop_na()関数を使うこともできる。これは()内で指定した変数が欠損している行をすべて除外する関数であり、複数の変数を同時に指定できるので便利である。たとえば、dfからPPPPolity_Scoreが欠損していない行だけを抽出し、CountryからPPP、そしてPolity_Score列を残してみよう。

Code 33
df |>
  drop_na(PPP, Polity_Score) |>
  select(Country:PPP, Polity_Score)
# A tibble: 155 × 6
   Country     Population    Area      GDP      PPP Polity_Score
   <chr>            <dbl>   <dbl>    <dbl>    <dbl>        <dbl>
 1 Afghanistan   38928346  652860   19101.   82737.           -1
 2 Albania        2877797   27400   15278.   39658.            9
 3 Algeria       43851044 2381740  169988.  496572.            2
 4 Angola        32866272 1246700   94635.  218533.           -2
 5 Argentina     45195774 2736690  449663. 1036721.            9
 6 Armenia        2963243   28470   13673.   38446.            7
 7 Australia     25499884 7682300 1392681. 1275027.           10
 8 Austria        9006398   82409  446315.  502771.           10
 9 Azerbaijan    10139177   82658   48048.  144556.           -7
10 Bahrain        1701575     760   38574.   74230.          -10
# ℹ 145 more rows

行のソート

 最後に行のソート(並び替え)について解説する。行のソートにはarrange()関数を使用する。第一引数はデータフレームのオブジェクトであり、第二引数以降は並び替えの基準となる変数名である。ソートは基本的には昇順、つまり値が小さい行が上に表示される。

 たとえば、dfからContinentの値が"Africa"の行のみを抽出し、Polity_Scoreが小さい国3を上位にする。そして、CountryPPP_per_capitaPolity_Score列のみ残すコードは以下の通りである。

Code 34
df |>
  filter(Continent == "Africa") |>
  arrange(Polity_Score) |>
  select(Country, PPP_per_capita, Polity_Score)
# A tibble: 54 × 3
   Country             PPP_per_capita Polity_Score
   <chr>                        <dbl>        <dbl>
 1 Eswatini                     8634.           -9
 2 Eritrea                      1860.           -7
 3 Equatorial Guinea           19458.           -6
 4 Cameroon                     3506.           -4
 5 Congo (Brazzaville)          3191.           -4
 6 Egypt                       11198.           -4
 7 Morocco                      7554.           -4
 8 Sudan                        4063.           -4
 9 Comoros                      3007.           -3
10 Congo (Kinshasa)             1043.           -3
# ℹ 44 more rows

 もし、Polity_Scoreが高い国を上位にしたい場合は、変数名をdesc()関数で囲む。

Code 35
df |>
  filter(Continent == "Africa") |>
  arrange(desc(Polity_Score)) |>
  select(Country, PPP_per_capita, Polity_Score)
# A tibble: 54 × 3
   Country      PPP_per_capita Polity_Score
   <chr>                 <dbl>        <dbl>
 1 Mauritius            22637.           10
 2 Kenya                 4105.            9
 3 South Africa         12605.            9
 4 Botswana             17311.            8
 5 Ghana                 5097.            8
 6 Lesotho               3019.            8
 7 Benin                 3067.            7
 8 Liberia               1461.            7
 9 Nigeria               5018.            7
10 Senegal               3248.            7
# ℹ 44 more rows

 しかし、Polity_Scoreが同点の場合はどうなるだろうか。この場合、第2の基準となる変数を指定すると、第1基準が同点の場合、第2基準変数の値に応じてソートされる。以下は、Polity_Scoreが高い国を上位にし、同点の場合はPPP_per_capitaが高い国を上位にするコード(さらに、変数名も変更)である。

Code 36
df |>
  filter(Continent == "Africa") |>
  arrange(desc(Polity_Score), desc(PPP_per_capita)) |>
  select(Country, PPP = PPP_per_capita, Polity = Polity_Score)
# A tibble: 54 × 3
   Country         PPP Polity
   <chr>         <dbl>  <dbl>
 1 Mauritius    22637.     10
 2 South Africa 12605.      9
 3 Kenya         4105.      9
 4 Botswana     17311.      8
 5 Ghana         5097.      8
 6 Lesotho       3019.      8
 7 Tunisia      10773.      7
 8 Nigeria       5018.      7
 9 Senegal       3248.      7
10 Benin         3067.      7
# ℹ 44 more rows

教科書

  1. 既存のオブジェクトに上書きすることも可能だが、あまり推奨しない。↩︎

  2. 関数によっては欠損値が含まれているケースを自動的に除外してくれる場合もある。↩︎

  3. Polity Scoreが高いほど、より民主主義に近いことを意味する。↩︎