第5回:データの整理(2)

2024-05-07

https://data-science-chiba-2024.github.io/day5/

データの整理(Data Wrangling)

  • データが手に入る時、まだ解析に使いえない状態が多い

  • データの整理(「wrangling」)は解析に使えるように整えるプロセス

    • 元々はカウボーイが牛を扱う技術から来る

Getty Images

data wrangling monsters

Image by Allison Horst

今日学ぶこと

  • データの集計
  • データの形の変え方(縦長いから横広いとその逆)
  • データの結合

データの集計

summarize()でデータの集計ができる:

library(nycflights13)
library(tidyverse)

summarize(flights, mean_sched_dep_time = mean(sched_dep_time))
# A tibble: 1 × 1
  mean_sched_dep_time
                <dbl>
1               1344.

グループ化

  • しかし、全体の平均だけを計算してもあまり意味はない。

  • それよりも、何か毎にデータの集計を行いたい方が多い。

  • それにはgroup_by()関数を使う。例えば、月ごと。まずはデータのグループを指定する:

flights %>%
  group_by(month)
# A tibble: 336,776 × 19
# Groups:   month [12]
    year month   day dep_time sched_dep_time dep_delay arr_time
   <int> <int> <int>    <int>          <int>     <dbl>    <int>
 1  2013     1     1      517            515         2      830
 2  2013     1     1      533            529         4      850
 3  2013     1     1      542            540         2      923
 4  2013     1     1      544            545        -1     1004
 5  2013     1     1      554            600        -6      812
 6  2013     1     1      554            558        -4      740
 7  2013     1     1      555            600        -5      913
 8  2013     1     1      557            600        -3      709
 9  2013     1     1      557            600        -3      838
10  2013     1     1      558            600        -2      753
# ℹ 336,766 more rows
# ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
#   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>,
#   dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
#   minute <dbl>, time_hour <dttm>

グループ化してから、集計する

次に、集計する:

flights |> 
  group_by(month) |> 
  summarize(
    avg_delay = mean(dep_delay)
  )
# A tibble: 12 × 2
   month avg_delay
   <int>     <dbl>
 1     1        NA
 2     2        NA
 3     3        NA
 4     4        NA
 5     5        NA
 6     6        NA
 7     7        NA
 8     8        NA
 9     9        NA
10    10        NA
11    11        NA
12    12        NA

あれ?なんでNAになってしまった?

  • NA(「Not Applicable」、「該当しない」)はデータが欠けていることを示す。
  • mean()一つでもデータが欠けていることがあると、結果は全部NAになってしまう。
  • 欠けているデータを無視して平均を計算するには、na.rm = TRUEを指定しないといけない
flights |> 
  group_by(month) |> 
  summarize(
    avg_delay = mean(dep_delay, na.rm = TRUE)
  )
# A tibble: 12 × 2
   month avg_delay
   <int>     <dbl>
 1     1     10.0 
 2     2     10.8 
 3     3     13.2 
 4     4     13.9 
 5     5     13.0 
 6     6     20.8 
 7     7     21.7 
 8     8     12.6 
 9     9      6.72
10    10      6.24
11    11      5.44
12    12     16.6 

どこがNAだったのかをfilter()で確かめる

flights |>
  filter(is.na(dep_delay))
# A tibble: 8,255 × 19
    year month   day dep_time sched_dep_time dep_delay arr_time
   <int> <int> <int>    <int>          <int>     <dbl>    <int>
 1  2013     1     1       NA           1630        NA       NA
 2  2013     1     1       NA           1935        NA       NA
 3  2013     1     1       NA           1500        NA       NA
 4  2013     1     1       NA            600        NA       NA
 5  2013     1     2       NA           1540        NA       NA
 6  2013     1     2       NA           1620        NA       NA
 7  2013     1     2       NA           1355        NA       NA
 8  2013     1     2       NA           1420        NA       NA
 9  2013     1     2       NA           1321        NA       NA
10  2013     1     2       NA           1545        NA       NA
# ℹ 8,245 more rows
# ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
#   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>,
#   dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
#   minute <dbl>, time_hour <dttm>

n()でグループの件数を数える

flights |> 
  group_by(month) |> 
  summarize(
    avg_delay = mean(dep_delay, na.rm = TRUE),
    n = n()
  )
# A tibble: 12 × 3
   month avg_delay     n
   <int>     <dbl> <int>
 1     1     10.0  27004
 2     2     10.8  24951
 3     3     13.2  28834
 4     4     13.9  28330
 5     5     13.0  28796
 6     6     20.8  28243
 7     7     21.7  29425
 8     8     12.6  29327
 9     9      6.72 27574
10    10      6.24 28889
11    11      5.44 27268
12    12     16.6  28135

ungroup()でグループを解除する

  • グループ毎の計算が終わったら、ungroup()でグループの解除をする必要がある場合がある。

(以下のコードでは自動的にグループが解除されるので本当はいらないけど、必要な場合もある)

flights |> 
  group_by(month) |> 
  summarize(
    avg_delay = mean(dep_delay, na.rm = TRUE)
  ) |>
  ungroup()
# A tibble: 12 × 2
   month avg_delay
   <int>     <dbl>
 1     1     10.0 
 2     2     10.8 
 3     3     13.2 
 4     4     13.9 
 5     5     13.0 
 6     6     20.8 
 7     7     21.7 
 8     8     12.6 
 9     9      6.72
10    10      6.24
11    11      5.44
12    12     16.6 

複数条件でグループ分けする

daily <-
  flights |>
  group_by(year, month, day)
summarize(daily, n = n())
# A tibble: 365 × 4
# Groups:   year, month [12]
    year month   day     n
   <int> <int> <int> <int>
 1  2013     1     1   842
 2  2013     1     2   943
 3  2013     1     3   914
 4  2013     1     4   915
 5  2013     1     5   720
 6  2013     1     6   832
 7  2013     1     7   933
 8  2013     1     8   899
 9  2013     1     9   902
10  2013     1    10   932
# ℹ 355 more rows

ungroup()でグループを解除する

daily |>
  ungroup() |>
  summarize(
    avg_delay = mean(dep_delay, na.rm = TRUE),
    n = n()
  )
# A tibble: 1 × 2
  avg_delay      n
      <dbl>  <int>
1      12.6 336776

データの形を変える

  • 同じデータを様々な形で表すことができる。

  • 例えば、以下の結核のデータを見ましょう(tidyversetidyrパッケージに入っている例のデータセット):

table1
# A tibble: 6 × 4
  country      year  cases population
  <chr>       <dbl>  <dbl>      <dbl>
1 Afghanistan  1999    745   19987071
2 Afghanistan  2000   2666   20595360
3 Brazil       1999  37737  172006362
4 Brazil       2000  80488  174504898
5 China        1999 212258 1272915272
6 China        2000 213766 1280428583
table2
# A tibble: 12 × 4
   country      year type            count
   <chr>       <dbl> <chr>           <dbl>
 1 Afghanistan  1999 cases             745
 2 Afghanistan  1999 population   19987071
 3 Afghanistan  2000 cases            2666
 4 Afghanistan  2000 population   20595360
 5 Brazil       1999 cases           37737
 6 Brazil       1999 population  172006362
 7 Brazil       2000 cases           80488
 8 Brazil       2000 population  174504898
 9 China        1999 cases          212258
10 China        1999 population 1272915272
11 China        2000 cases          213766
12 China        2000 population 1280428583

データの形を変える

  • 元々のデータが同じであることは確認できましたか?

  • でも、table1table2には大事な違いがある:

    • table1だけがtidy(すぐに解析できるように整っている)になっている

tidyなデータの定義

  • 各列が変数を表している
  • 各行が観察を表している
  • 各セルが一つの値を表している

tidyなデータの利点

  • 今まで学んできた関数(mutate()filter()など)はtidyなデータの方が使いやすい
  • グラフを作成する際も、tidyなデータの方が使いやすいです

例えば:

# 1万人あたりの病気の発生率を計算する
table1 |>
  mutate(rate = cases / population * 10000)
# A tibble: 6 × 5
  country      year  cases population  rate
  <chr>       <dbl>  <dbl>      <dbl> <dbl>
1 Afghanistan  1999    745   19987071 0.373
2 Afghanistan  2000   2666   20595360 1.29 
3 Brazil       1999  37737  172006362 2.19 
4 Brazil       2000  80488  174504898 4.61 
5 China        1999 212258 1272915272 1.67 
6 China        2000 213766 1280428583 1.67 
ggplot(table1, aes(x = year, y = cases)) +
  geom_line(aes(group = country)) +
  geom_point(aes(shape = country, color = country), size = 4)

ワイド(横広い)データを縦長(ロング)に変える

billboardというデータセットには2000年の歌のランキングが入っている:

billboard
# A tibble: 317 × 79
   artist track date.entered   wk1   wk2   wk3   wk4   wk5   wk6   wk7
   <chr>  <chr> <date>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1 2 Pac  Baby… 2000-02-26      87    82    72    77    87    94    99
 2 2Ge+h… The … 2000-09-02      91    87    92    NA    NA    NA    NA
 3 3 Doo… Kryp… 2000-04-08      81    70    68    67    66    57    54
 4 3 Doo… Loser 2000-10-21      76    76    72    69    67    65    55
 5 504 B… Wobb… 2000-04-15      57    34    25    17    17    31    36
 6 98^0   Give… 2000-08-19      51    39    34    26    26    19     2
 7 A*Tee… Danc… 2000-07-08      97    97    96    95   100    NA    NA
 8 Aaliy… I Do… 2000-01-29      84    62    51    41    38    35    35
 9 Aaliy… Try … 2000-03-18      59    53    38    28    21    18    16
10 Adams… Open… 2000-08-26      76    76    74    69    68    67    61
# ℹ 307 more rows
# ℹ 69 more variables: wk8 <dbl>, wk9 <dbl>, wk10 <dbl>, wk11 <dbl>,
#   wk12 <dbl>, wk13 <dbl>, wk14 <dbl>, wk15 <dbl>, wk16 <dbl>,
#   wk17 <dbl>, wk18 <dbl>, wk19 <dbl>, wk20 <dbl>, wk21 <dbl>,
#   wk22 <dbl>, wk23 <dbl>, wk24 <dbl>, wk25 <dbl>, wk26 <dbl>,
#   wk27 <dbl>, wk28 <dbl>, wk29 <dbl>, wk30 <dbl>, wk31 <dbl>,
#   wk32 <dbl>, wk33 <dbl>, wk34 <dbl>, wk35 <dbl>, wk36 <dbl>, …

ワイドなデータの例:billboard

  • 列:
    • artist アーティスト(演奏者)
    • track 曲名
    • date.entered 最初にランキングに入った日付
    • wk1 .. wk76 それぞれの種のランキング

列がやたらある。そして、列の名前にデータがある(周目)。これをどうやって別の変数にできるのでしょうか?

ワイドからロングにするのはpivot_longer()

  • pivot_longer()の主な引数:
    • cols どの列を使う(ロングにする)
    • names_to ロング形式に変換される新しい列の名前
    • values_to 新しい値の列の名前

でや、やって見ましょう

billboard |>
  pivot_longer(
    cols = starts_with("wk"),
    names_to = "week",
    values_to = "rank"
  )
# A tibble: 24,092 × 5
   artist track                   date.entered week   rank
   <chr>  <chr>                   <date>       <chr> <dbl>
 1 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk1      87
 2 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk2      82
 3 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk3      72
 4 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk4      77
 5 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk5      87
 6 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk6      94
 7 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk7      99
 8 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk8      NA
 9 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk9      NA
10 2 Pac  Baby Don't Cry (Keep... 2000-02-26   wk10     NA
# ℹ 24,082 more rows

列の数が大幅に減り、行の数が増えたことが分かりますか?

ロングなデータをワイドに変える

cms_patient_experienceというデータセットには患者の経験のデータが入っている

cms_patient_experience
# A tibble: 500 × 5
   org_pac_id org_nm                 measure_cd measure_title prf_rate
   <chr>      <chr>                  <chr>      <chr>            <dbl>
 1 0446157747 USC CARE MEDICAL GROU… CAHPS_GRP… CAHPS for MI…       63
 2 0446157747 USC CARE MEDICAL GROU… CAHPS_GRP… CAHPS for MI…       87
 3 0446157747 USC CARE MEDICAL GROU… CAHPS_GRP… CAHPS for MI…       86
 4 0446157747 USC CARE MEDICAL GROU… CAHPS_GRP… CAHPS for MI…       57
 5 0446157747 USC CARE MEDICAL GROU… CAHPS_GRP… CAHPS for MI…       85
 6 0446157747 USC CARE MEDICAL GROU… CAHPS_GRP… CAHPS for MI…       24
 7 0446162697 ASSOCIATION OF UNIVER… CAHPS_GRP… CAHPS for MI…       59
 8 0446162697 ASSOCIATION OF UNIVER… CAHPS_GRP… CAHPS for MI…       85
 9 0446162697 ASSOCIATION OF UNIVER… CAHPS_GRP… CAHPS for MI…       83
10 0446162697 ASSOCIATION OF UNIVER… CAHPS_GRP… CAHPS for MI…       63
# ℹ 490 more rows

cms_patient_experienceの特徴

  • 研究の対象となっているのは組織だけど、各組織のデータは6つの行を跨いでいる。

  • どうやって一つの組織を一つの列にできる?

ロングからワイドにするのはpivot_wider()

  • pivot_wider()の主な引数:
    • cols どの列を使う(ワイドにする)
    • names_from 列名として使用するロング形式の列名
    • values_from 新しい列に配置される値を持つロング形式の列

でや、やって見ましょう

cms_patient_experience |>
  pivot_wider(
    names_from = measure_cd,
    values_from = prf_rate
  )
# A tibble: 500 × 9
   org_pac_id org_nm measure_title CAHPS_GRP_1 CAHPS_GRP_2 CAHPS_GRP_3
   <chr>      <chr>  <chr>               <dbl>       <dbl>       <dbl>
 1 0446157747 USC C… CAHPS for MI…          63          NA          NA
 2 0446157747 USC C… CAHPS for MI…          NA          87          NA
 3 0446157747 USC C… CAHPS for MI…          NA          NA          86
 4 0446157747 USC C… CAHPS for MI…          NA          NA          NA
 5 0446157747 USC C… CAHPS for MI…          NA          NA          NA
 6 0446157747 USC C… CAHPS for MI…          NA          NA          NA
 7 0446162697 ASSOC… CAHPS for MI…          59          NA          NA
 8 0446162697 ASSOC… CAHPS for MI…          NA          85          NA
 9 0446162697 ASSOC… CAHPS for MI…          NA          NA          83
10 0446162697 ASSOC… CAHPS for MI…          NA          NA          NA
# ℹ 490 more rows
# ℹ 3 more variables: CAHPS_GRP_5 <dbl>, CAHPS_GRP_8 <dbl>,
#   CAHPS_GRP_12 <dbl>

ふむ、NAが多い、行の数がまだ変わっていない。どうすれば良い・・

id_colsで行を識別する列を指定する

cms_patient_experience |>
  pivot_wider(
    names_from = measure_cd,
    values_from = prf_rate,
    id_cols = starts_with("org")
  )
# A tibble: 95 × 8
   org_pac_id org_nm   CAHPS_GRP_1 CAHPS_GRP_2 CAHPS_GRP_3 CAHPS_GRP_5
   <chr>      <chr>          <dbl>       <dbl>       <dbl>       <dbl>
 1 0446157747 USC CAR…          63          87          86          57
 2 0446162697 ASSOCIA…          59          85          83          63
 3 0547164295 BEAVER …          49          NA          75          44
 4 0749333730 CAPE PH…          67          84          85          65
 5 0840104360 ALLIANC…          66          87          87          64
 6 0840109864 REX HOS…          73          87          84          67
 7 0840513552 SCL HEA…          58          83          76          58
 8 0941545784 GRITMAN…          46          86          81          54
 9 1052612785 COMMUNI…          65          84          80          58
10 1254237779 OUR LAD…          61          NA          NA          65
# ℹ 85 more rows
# ℹ 2 more variables: CAHPS_GRP_8 <dbl>, CAHPS_GRP_12 <dbl>

データの結合

  • 多くの場合、使いたいデータが複数のデータセット(データフレーム、あるいはファイル)に跨いで入っている

  • したがって、データの結合(join)を行う必要がある

複数のデータセットの例:飛行機のデータ

airlinesには航空会社の名前のデータが入っている:

airlines
# A tibble: 16 × 2
   carrier name                       
   <chr>   <chr>                      
 1 9E      Endeavor Air Inc.          
 2 AA      American Airlines Inc.     
 3 AS      Alaska Airlines Inc.       
 4 B6      JetBlue Airways            
 5 DL      Delta Air Lines Inc.       
 6 EV      ExpressJet Airlines Inc.   
 7 F9      Frontier Airlines Inc.     
 8 FL      AirTran Airways Corporation
 9 HA      Hawaiian Airlines Inc.     
10 MQ      Envoy Air                  
11 OO      SkyWest Airlines Inc.      
12 UA      United Air Lines Inc.      
13 US      US Airways Inc.            
14 VX      Virgin America             
15 WN      Southwest Airlines Co.     
16 YV      Mesa Airlines Inc.         

どうやってこのデータをnycflights13と結合させることができるのでしょうか?

付け加えたデータが右の方に入る

nycflights13は列の数が多いことを覚えていますか?

  • これから新しいデータと結合しても、付け加えたデータが右の方に入るため、結果が見えないので、説明するために小さいバージョンを用意する
flights2 <- flights |>
  select(year, time_hour, origin, dest, tailnum, carrier)

別のデータフレームからデータを加える:left_join()

航空会社の名前を追加しましょう:

flights2 |>
  left_join(airlines)
# A tibble: 336,776 × 7
    year time_hour           origin dest  tailnum carrier name        
   <int> <dttm>              <chr>  <chr> <chr>   <chr>   <chr>       
 1  2013 2013-01-01 05:00:00 EWR    IAH   N14228  UA      United Air …
 2  2013 2013-01-01 05:00:00 LGA    IAH   N24211  UA      United Air …
 3  2013 2013-01-01 05:00:00 JFK    MIA   N619AA  AA      American Ai…
 4  2013 2013-01-01 05:00:00 JFK    BQN   N804JB  B6      JetBlue Air…
 5  2013 2013-01-01 06:00:00 LGA    ATL   N668DN  DL      Delta Air L…
 6  2013 2013-01-01 05:00:00 EWR    ORD   N39463  UA      United Air …
 7  2013 2013-01-01 06:00:00 EWR    FLL   N516JB  B6      JetBlue Air…
 8  2013 2013-01-01 06:00:00 LGA    IAD   N829AS  EV      ExpressJet …
 9  2013 2013-01-01 06:00:00 JFK    MCO   N593JB  B6      JetBlue Air…
10  2013 2013-01-01 06:00:00 LGA    ORD   N3ALAA  AA      American Ai…
# ℹ 336,766 more rows

結合する時は「鍵」が大事

上のコードを走らせた際、このメッセージがありました:

Joining with `by = join_by(carrier)`
  • つまり、それぞれのデータフレームが共通に持っている列です。これ「鍵」(Key)と呼ぶ。

  • 共通に持っている列がないと、結合ができない。

結合する時は「鍵」が大事

上のコードは自動的に共通する列を鍵として使ったけど、手動で指定することもできる:

flights2 |>
  left_join(
    airlines,
    by = join_by(carrier))
# A tibble: 336,776 × 7
    year time_hour           origin dest  tailnum carrier name        
   <int> <dttm>              <chr>  <chr> <chr>   <chr>   <chr>       
 1  2013 2013-01-01 05:00:00 EWR    IAH   N14228  UA      United Air …
 2  2013 2013-01-01 05:00:00 LGA    IAH   N24211  UA      United Air …
 3  2013 2013-01-01 05:00:00 JFK    MIA   N619AA  AA      American Ai…
 4  2013 2013-01-01 05:00:00 JFK    BQN   N804JB  B6      JetBlue Air…
 5  2013 2013-01-01 06:00:00 LGA    ATL   N668DN  DL      Delta Air L…
 6  2013 2013-01-01 05:00:00 EWR    ORD   N39463  UA      United Air …
 7  2013 2013-01-01 06:00:00 EWR    FLL   N516JB  B6      JetBlue Air…
 8  2013 2013-01-01 06:00:00 LGA    IAD   N829AS  EV      ExpressJet …
 9  2013 2013-01-01 06:00:00 JFK    MCO   N593JB  B6      JetBlue Air…
10  2013 2013-01-01 06:00:00 LGA    ORD   N3ALAA  AA      American Ai…
# ℹ 336,766 more rows

鍵となる列の条件

  • 右側のデータの鍵が行を識別できる必要がある。それぞれのcarrierはデータに一回だけ出てくる

  • count()で確かめる(nが全て1になっている)

airlines |>
  count(carrier)
# A tibble: 16 × 2
   carrier     n
   <chr>   <int>
 1 9E          1
 2 AA          1
 3 AS          1
 4 B6          1
 5 DL          1
 6 EV          1
 7 F9          1
 8 FL          1
 9 HA          1
10 MQ          1
11 OO          1
12 UA          1
13 US          1
14 VX          1
15 WN          1
16 YV          1

他のデータに結合してみる

nycflights13パッケージに他のデータフレームがいくつかある:

チャレンジ:他のデータに結合してみる

planesのデータをflights2に結合することができますか?