第7章 ロング・ワイドの変換

データの読み込みで世界銀行の一人あたりGDPのデータを読み込んだ際に、ワイド形式なのでロング形式にしないといけないと書きました。

ここではワイド形式からロング形式にする方法について、

  1. ループ処理を使う方法
  2. tidyverseの中のtidyrの関数を使う方法

を説明しますが、その前になぜそうしなければいけないのかについて簡単に説明します。

library(tidyverse)

7.1 パネルデータ

ある一時点のいくつかのユニットのデータをクロスセクションあるいは横断データと呼びます。 例えば、2019年の各国の一人あたりGDPデータはクロスセクションデータと言えます。

逆にあるユニットの複数時点のデータを時系列データと呼びます。 例えば、日本の1950年から2019年までの一人あたりGDPデータは時系列データと言えます。

パネルデータ、縦断データあるいはTSCS (Time-Series Cross-Section) データと呼ばれるものは複数のユニットの複数時点のデータになります。 今回扱うデータはパネルデータになります。

7.1.1 ワイド形式

再び、世界銀行のデータを読み込みます。

data <- read_csv("data/wb_gdp_pc.csv", skip = 4)
## Warning: Missing column names filled in: 'X64' [64]
## Parsed with column specification:
## cols(
##   .default = col_double(),
##   `Country Name` = col_character(),
##   `Country Code` = col_character(),
##   `Indicator Name` = col_character(),
##   `Indicator Code` = col_character(),
##   `2018` = col_logical(),
##   X64 = col_logical()
## )
## See spec(...) for full column specifications.
head(data)

中身を見てみると各行はユニット(この場合は国家)で各列がそれぞれの年の一人あたりGDPになっていることがわかります。 このように時間が進むに連れて横に増えていくデータをワイド形式と呼びます。

7.1.2 ロング形式

結論を先取りしていうと、パネルデータ分析をする際にはデータはロング形式である必要があります。

ロング形式では各行はユニットかつ時点となっており、それぞれのユニットの特定の時点の変数の値(今回の場合はgdp_pc)が格納されています。

  • 整然 (tidy) データについてはこちらを参照。

7.1.3 前処理

以下ではワイド形式からロング形式に変換する方法を説明しますが、その準備としてデータをきれいな形に変換します。

まず、変数名を確認すると次の点に気づきます。

  • Coutnry Code, Indicator NameIndicator Codeという変数が不要でありそう。
  • Country Nameという変数名に空白があると面倒なので名前を変えた方がいい。
  • X64という謎の変数がある。
names(data)
##  [1] "Country Name"   "Country Code"   "Indicator Name" "Indicator Code"
##  [5] "1960"           "1961"           "1962"           "1963"          
##  [9] "1964"           "1965"           "1966"           "1967"          
## [13] "1968"           "1969"           "1970"           "1971"          
## [17] "1972"           "1973"           "1974"           "1975"          
## [21] "1976"           "1977"           "1978"           "1979"          
## [25] "1980"           "1981"           "1982"           "1983"          
## [29] "1984"           "1985"           "1986"           "1987"          
## [33] "1988"           "1989"           "1990"           "1991"          
## [37] "1992"           "1993"           "1994"           "1995"          
## [41] "1996"           "1997"           "1998"           "1999"          
## [45] "2000"           "2001"           "2002"           "2003"          
## [49] "2004"           "2005"           "2006"           "2007"          
## [53] "2008"           "2009"           "2010"           "2011"          
## [57] "2012"           "2013"           "2014"           "2015"          
## [61] "2016"           "2017"           "2018"           "X64"

そこで、X64の中身を確認します。

data$X64
##   [1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
##  [26] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
##  [51] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
##  [76] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [101] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [126] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [151] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [176] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [201] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [226] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [251] NA NA NA NA NA NA NA NA NA NA NA NA NA NA

どうやら取り除いていい変数のようなので以下の処理を行います。

data <- select(data,
               -"Country Code", -"Indicator Name", -"Indicator Code", -X64)
data <- rename(data, name = "Country Name")
names(data)
##  [1] "name" "1960" "1961" "1962" "1963" "1964" "1965" "1966" "1967" "1968"
## [11] "1969" "1970" "1971" "1972" "1973" "1974" "1975" "1976" "1977" "1978"
## [21] "1979" "1980" "1981" "1982" "1983" "1984" "1985" "1986" "1987" "1988"
## [31] "1989" "1990" "1991" "1992" "1993" "1994" "1995" "1996" "1997" "1998"
## [41] "1999" "2000" "2001" "2002" "2003" "2004" "2005" "2006" "2007" "2008"
## [51] "2009" "2010" "2011" "2012" "2013" "2014" "2015" "2016" "2017" "2018"

いい感じになりました。

7.2 ループによる方法

7.2.1 for文

ループ処理とは似たような操作を何度も繰り返すことです。 例えば、1から10までの数字を表示させるループ処理は次のようなコードでできます。

for (i in 1:10) {
  print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10

具体的に何を行っているのでしょうか? まず、1:101から10まで1刻みに増加するベクトルを作成しています。

1:10
##  [1]  1  2  3  4  5  6  7  8  9 10

そして、i in 1:101:10の要素を順番にiに代入し、後ろの{}内の処理を行うということを意味しています。

  • まず、i1が代入され、print(i)によってiの中身の1が表示されます。
  • 処理が終わるとi2が代入され、print(i)によってiの中身の2が表示されます。
  • 以上の処理がi10が代入され、その処理が終わるまで続きます。

なお、inの左側はiである必要はなく、右側も数値である必要はありません。

letters[1:10]
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
for (j in letters[1:10]) {
  print(j)
}
## [1] "a"
## [1] "b"
## [1] "c"
## [1] "d"
## [1] "e"
## [1] "f"
## [1] "g"
## [1] "h"
## [1] "i"
## [1] "j"

7.2.2 時点の選択による方法

ループで変換する場合、2つの戦略があると思われます。 つまり、

  1. 時点を順番に選択してくっつけていく方法
  2. ユニットを順番に選択してくっつけていく方法

です。 まずは前者の方法を行います。

データの変数名の内、最初のものを除いたものが時点になっているので、yearとしてベクトルを作成します。

year <- names(data)[-1]
year
##  [1] "1960" "1961" "1962" "1963" "1964" "1965" "1966" "1967" "1968" "1969"
## [11] "1970" "1971" "1972" "1973" "1974" "1975" "1976" "1977" "1978" "1979"
## [21] "1980" "1981" "1982" "1983" "1984" "1985" "1986" "1987" "1988" "1989"
## [31] "1990" "1991" "1992" "1993" "1994" "1995" "1996" "1997" "1998" "1999"
## [41] "2000" "2001" "2002" "2003" "2004" "2005" "2006" "2007" "2008" "2009"
## [51] "2010" "2011" "2012" "2013" "2014" "2015" "2016" "2017" "2018"

いきなりループを書くのは大変なので、まずはiに具体的な数値を入れてちゃんとうまく行くかを確かめます。 例えば、1960年のデータを抜き出したい場合は、そのデータを一時的に保存するものとしてtemp(temporaryの気持ち)とすると

i <- "1960"
temp <- data[c("name", i)]
head(temp)

とすればいいことがわかります。

また、ロング形式では時点の変数も必要なので、新たにyear変数を作成する必要もあります。

temp$year <- i
head(temp)

ただし、このままではyearが文字列となっているので、as.numeric()で数値データに変換します。

temp$year <- as.numeric(i)
head(temp)

さて、これを縦方向に繋げていけばいいのですが、一人あたりGDPの変数名が年(この場合は1960)のままなので、他の年のデータと繋げるときに面倒なことになります。 なので、変数の名前を各データで共通のものgdp_pcにしておく必要があります。

names(temp)[names(temp) == i] <- "gdp_pc"
head(temp)

最後にデータを結合する方法ですが、縦方向に結合する場合はbind_rows()を使います。

ここで、ややプログラミング特有の方法があります。 それは、まずからのデータを作成し、それにどんどん繋げていく方法です。 つまり、まずは変換後のデータを入れる空のオブジェクトとしてdata_longを作っておきます。

data_long <- NULL

そして、前述の処理をfor文として書きます。 ただし、例として1960としていた部分はループ処理の中で変化していくので除外しておきます。

for (i in year) {
  temp <- data[c("name", i)]
  temp$year <- as.numeric(i)
  names(temp)[names(temp) == i] <- "gdp_pc"
  data_long <- bind_rows(data_long, temp)
}

新たに加わった処理はdata_long <- bind_rows(data_long, temp)です。 ここではdata_longに各年のロング形式のデータをくっつけて、それをdata_longとして上書きしています。

  • まず、空のdata_longが存在します。
  • yearの第1要素は1960なので、1960年のデータがワイド形式になってtempに保存されます。
  • 最後に空のdata_longと1960年のtempが結合され、新しいdata_longとして保存されます。
  • 次にyearの第2要素は1961年なので1961年のデータがtempに保存されます。
  • 最後に1960年のデータが入ったdata_longと1961年のデータであるtempが結合され、1960年と1961年のデータが入ったdata_longとして上書きされます。
  • 以上の処理がyearの全ての要素について行われます。

実際にロング形式に変換されているのがわかります。

head(data_long)

この方法で注意すべきなのはfor文を実行する前に新しいデータの保存先(今回の場合はdata_long)を初期化することです。 初期化を忘れて再びfor文を実行すると同じデータが重複して結合されてしまいます。

7.2.3 ユニットの選択による方法

次はユニットを選択して同様にループ処理を行います。 まず、1行目のデータを抜き出すと次のようになっています。

i <- 1
data[i,]

よって、国名はdata[1,1]にあり、一人あたりGDPデータは`data[1,-1]’にあることがわかります。

data[i,1]
data[i,-1]

ところで、新たにデータセットを作る場合にはdata.frame()tidyverse版はtibble())を使います。 ()に中で変数名を指定し、=の後ろで変数を定義します。

また、=の後ろはデータフレームではいけないので国名はas.character()で、一人あたりGDPと年はas.numeric()でベクトルにしておきます。

したがって、Arubaのデータをロング形式にしたものをtempとすると、

temp <- tibble(name = as.character(data[i,1]),
               year = as.numeric(year),
               gdp_pc = as.numeric(data[i,-1]))
head(temp)

で変換できます。

あとは、先ほどと同様にfor文でくっつけていきます。 行の数だけfor文を回すのでnrow()で回数を決めます。

data_long <- NULL
for (i in 1:nrow(data)) {
  temp <- tibble(name = as.character(data[i,1]),
               year = as.numeric(year),
               gdp_pc = as.numeric(data[i,-1]))
  data_long <- bind_rows(data_long, temp)
}
head(data_long)

7.3 tidyverseな方法

7.3.1 ワイドからロングへ

tidyversrtidyrpivot_longer()という関数を使うとワイド形式からロング形式に変換することができます。 慣れると簡単ですが、最初はとっつきにくいかもしれません。

結論から言うと、次のたった一行のコードで変換することができます。

data <- data %>% 
  pivot_longer(-name, names_to = "year", values_to = "gdp_pc")
data %>% 
  drop_na() %>% 
  head()

さて、pivot_longer()の引数は最初のデータフレームを除いて3つあります。 最後の2つは必須の入力引数です。

  1. まず、-nameですが、これはロング形式にする際にkey変数として使わない変数を-で指定しています。 パネルデータの場合、年に漢検なく値が不変の変数を除外する必要があります。 今回は国名を除外しました。
  2. 続いて、names_toですが、これはワイド形式における変数名(この場合は年)を新しい変数として作成する際の変数名です。 なので、ここではyearとしました。
  3. 最後に、values_toはワイド形式における変数の値(この場合は各年の一人あたりGDP)を新しい変数として作成する際の変数名です。 なので、ここではgdp_pcとしました。

ところで、実は処理はまだ終わっていません。 よく見るとyearが文字列になっているのが分かります。 なぜなら、もともと変数名だったからです。 なので、as.numeric()で数値データに変換する必要があります。

data <- data %>% 
  mutate(year = as.numeric(year))
data %>% 
  drop_na() %>% 
  head()

7.3.2 パイプ

ところで、tidyverseの中のmagritrには%>%という代入演算子(パイプ)があります。 これは、左のオブジェクトを右の関数の第1引数にする役割を果たします。

  • %>%はRStudioではShift + Ctrl + mで入力できます。

パイプによって、以上の処理は次のようにまとめて書くこともできます。

data <- read_csv("data/wb_gdp_pc.csv", skip = 4)
## Warning: Missing column names filled in: 'X64' [64]
## Parsed with column specification:
## cols(
##   .default = col_double(),
##   `Country Name` = col_character(),
##   `Country Code` = col_character(),
##   `Indicator Name` = col_character(),
##   `Indicator Code` = col_character(),
##   `2018` = col_logical(),
##   X64 = col_logical()
## )
## See spec(...) for full column specifications.
data <- select(data,
               -"Country Code", -"Indicator Name", -"Indicator Code", -X64)
data <- rename(data, name = "Country Name")
data <- data %>% 
  pivot_longer(-name, names_to = "year", values_to = "gdp_pc") %>% 
  mutate(year = as.numeric(year))
data %>% 
  drop_na() %>% 
  head()

もっと欲張れば、これも全てパイプで繋ぐことができます。

data <- read_csv("data/wb_gdp_pc.csv", skip = 4) %>% 
  select(-"Country Code", -"Indicator Name", -"Indicator Code", -X64)%>% 
  rename(name = "Country Name") %>% 
  pivot_longer(-name, names_to = "year", values_to = "gdp_pc") %>% 
  mutate(year = as.numeric(year))
## Warning: Missing column names filled in: 'X64' [64]
## Parsed with column specification:
## cols(
##   .default = col_double(),
##   `Country Name` = col_character(),
##   `Country Code` = col_character(),
##   `Indicator Name` = col_character(),
##   `Indicator Code` = col_character(),
##   `2018` = col_logical(),
##   X64 = col_logical()
## )
## See spec(...) for full column specifications.
data %>% 
  drop_na() %>% 
  head()

7.3.3 tidyverseの利点

tidyverseは今、Rで非常に人気のあるパッケージで少なくない人がtidyverseの関数を使い、パイプでコードを書いています。 もちろん、tidyverseを使わない人もいますし、使わないといけないわけでもありません。

  • 必要以上に特定のパッケージに依存するのは危険だと思います。

ここではtidyverseを使うことの利点をまとめておきます。

  • コードが短くなる。
  • ループでは必要だったdata_longのような保存先を作る必要がない。
  • パイプを使うと処理の順番にコードが書かれているので分かりやすい。

7.3.4 ロングからワイドへ

ちなみに、tidyrpivot_wider()を使うとロング形式からワイド形式に変換できます。

data <- data %>% 
  pivot_wider(names_from = "year", values_from = "gdp_pc")
data
names(data)
##  [1] "name" "1960" "1961" "1962" "1963" "1964" "1965" "1966" "1967" "1968"
## [11] "1969" "1970" "1971" "1972" "1973" "1974" "1975" "1976" "1977" "1978"
## [21] "1979" "1980" "1981" "1982" "1983" "1984" "1985" "1986" "1987" "1988"
## [31] "1989" "1990" "1991" "1992" "1993" "1994" "1995" "1996" "1997" "1998"
## [41] "1999" "2000" "2001" "2002" "2003" "2004" "2005" "2006" "2007" "2008"
## [51] "2009" "2010" "2011" "2012" "2013" "2014" "2015" "2016" "2017" "2018"