(これは完全な Tidy data paper の非公式でコードの重いバージョンです。 詳細はそちらを参照してください)。
データ分析の80%はデータの整理・準備に費やされるとよく言われます。 しかも、それは最初の一歩だけではなく、分析の過程で新たな問題が明らかになったり、新しいデータが収集されたりするたびに、何度も繰り返さなければなりません。 この問題を把握するために、本稿ではデータクリーニングの中でも、小さいながらも重要な側面に焦点を当て、私はそれをデータの ** tidying** (整頓) と呼んでいます。 つまり、分析しやすいようにデータセットを構造化することです。
tidy data の原則は、データセット内のデータ値を整理する標準的な方法を提供します。 標準規格があれば、毎回ゼロから始めて車輪を再発明する必要がないため、最初のデータクリーニングが容易になります。 tidy data standard は、データの初期探索と分析を容易にし、連携するデータ分析ツールの開発を簡素化するために設計されています。 現在のツールは翻訳が必要な場合が多い。 あるツールからの出力を別のツールに入力できるようにするために、時間をかけて咀嚼しなければなりません。 整頓されたデータセットと整頓されたツールは、手を取り合ってデータ分析を容易にし、興味のないデータのロジスティックスではなく、興味深い分野の問題に集中することができます。
Happy families are all alike; every unhappy family is unhappy in its own way — Leo Tolstoy
家族のように、整頓されたデータセットはみな似ているが、混乱したデータセットはどれもそれなりに混乱している。 tidy データセットは、データセットの構造(物理的なレイアウト)とセマンティクス(意味)を結びつける標準的な方法を提供すします この節では、データセットの構造とセマンティクスを記述するための標準的な語彙を提供し、それらの定義を使って tidy データを定義します。
ほとんどの統計データは、行と列で構成されるデータフレームです。 列にはほとんどの場合ラベルが貼られ、行には時々ラベルが貼られます。 次のコードは、想像上の教室に関するいくつかのデータを、一般的に見られる形式で提供しています。 表は3つの列と4つの行を持ち、行と列の両方にラベルが付けられています。
<- read.csv("classroom.csv", stringsAsFactors = FALSE)
classroom
classroom#> name quiz1 quiz2 test1
#> 1 Billy <NA> D C
#> 2 Suzy F <NA> <NA>
#> 3 Lionel B C B
#> 4 Jenny A A B
同じ基礎データを構造化するには様々な方法があります。 次の表は、上記と同じデータを示していますが、行と列が入れ替わっています。
read.csv("classroom2.csv", stringsAsFactors = FALSE)
#> assessment Billy Suzy Lionel Jenny
#> 1 quiz1 <NA> FALSE B A
#> 2 quiz2 D NA C A
#> 3 test1 C NA B B
データは同じですが、レイアウトが異なります。この2つのテーブルがなぜ同じデータを表しているのかを説明するには、行と列の語彙が不足しています。見た目だけでなく、テーブルに表示される値の基本的なセマンティクス(意味)を説明する方法が必要です。
データセットは値の集まりで、通常は数字(定量的な場合)または文字列(定性的な場合)です。値は2つの方法で整理されます。 すべての値は 変数 と 観測 に属する。 変数には、同じ基本属性(高さ、温度、時間など)を単位ごとに測定するすべての値が含まれます。 観測値は、属性を超えて同じ単位(人、日、レースなど)で測定されたすべての値を含みます。
教室のデータを整理すると、次のようになります。(関数がどのように機能するかは後ほど説明します)
library(tidyr)
library(dplyr)
<- classroom %>%
classroom2 pivot_longer(quiz1:test1, names_to = "assessment", values_to = "grade") %>%
arrange(name, assessment)
classroom2#> # A tibble: 12 × 3
#> name assessment grade
#> <chr> <chr> <chr>
#> 1 Billy quiz1 <NA>
#> 2 Billy quiz2 D
#> 3 Billy test1 C
#> 4 Jenny quiz1 A
#> 5 Jenny quiz2 A
#> 6 Jenny test1 B
#> 7 Lionel quiz1 B
#> 8 Lionel quiz2 C
#> 9 Lionel test1 B
#> 10 Suzy quiz1 F
#> # … with 2 more rows
これにより、値、変数、観測値がより明確になります。データセットには、3つの変数を表す36個の値と12個の観測値が含まれています。 変数は
name
, 4つの可能な値(Billy, Suzy, Lionel, Jenny)を持つ。
assessment
, 3つの値を持つ (quiz1, quiz2, test1).
grade
: 欠損値をどのように考えるかによって、5つまたは6つの値があります (A, B, C, D, F, NA)。
整理されたデータフレームは、観測の定義を明示的に教えてくれます。 この教室では、name
とassessment
のすべての組み合わせが、1つの測定された観測値です。 また、データセットは欠損値についても教えてくれますが、これには意味があります。 Billy は最初の小テストを欠席したが、成績を挽回しようとした。 Suzy は最初の小テストに失敗したので、そのクラスをやめることにしました。 Billy の最終成績を計算するために、この欠損値をFに置き換えるかもしれません(あるいは、小テストを受ける2回目のチャンスを得るかもしれません)。 しかし、テスト1のクラス平均を知りたいのであれば、新しい値を入力するよりも、Suzy の構造的な欠損値を削除する方が適切です。
あるデータセットにおいて、何がオブザベーションで何が変数なのかを把握するのは通常簡単ですが、一般的に変数やオブザベーションを正確に定義するのは意外と難しいものです。例えば、教室のデータの列が height
と weight
だったら、喜んで変数と呼んでいたでしょう。 もし、列が「高さ」と「幅」だったら、高さと幅を「次元」変数の値と考えるかもしれないので、あまり明確ではないでしょう。 列が home phone
と work phone
であれば、これらを2つの変数として扱うことができますが、不正行為を検出する環境では、1つの電話番号を複数の人が使用することは不正行為を示唆する可能性があるため、phone number
と number type
という変数が必要になるかもしれません。 一般的な経験則としては、行間よりも変数間の関数的関係(例えば、z
はx
とy
の線形結合であり、density
はweight
とvolume
の比である)を記述する方が簡単であり、列のグループ間よりもオブザベーションのグループ間の比較(例えば,グループaの平均とグループbの平均)を行う方が簡単である.
ある分析では、複数の観測レベルが存在する可能性があります。例えば、新しいアレルギー薬の試験では、3つの観測タイプがあるかもしれません: 各人から収集した人口統計学的データ(age
、sex
、race
)、各日に各人から収集した医学的データ(number of sneezes
、redness of eyes
)、各日に収集した気象学的データ(temperature
、pollen count
)です。
変数は分析中に変更される可能性があります。 多くの場合、生データの変数は非常に細かいものであり、説明的な利益が少ないにもかかわらず、モデル化の複雑さが増してしまうことがあります。 例えば、多くの調査では、基本的な特性をよりよく把握するために、同じ質問のバリエーションを尋ねます。 分析の初期段階では、変数は質問に対応しています。 後半の段階では、複数の質問を平均化して算出された特性に焦点を移します。 これにより、階層モデルを必要とせず、データが離散的ではなく連続的であるとみなすことができるため、分析が大幅に簡素化されます。
tidy data とは、データセットの意味をその構造にマッピングする標準的な方法です。 データセットは、行、列、表が観測値、変数、型とどのようにマッチしているかによって、雑然としたり整然としたりする。 整頓されたデータ では
すべての列が変数である。
すべての行はオブザベーションである。
すべてのセルは1つの値である。
これはコッドの第3正規形であるが、制約は統計的な言語で表現され、リレーショナルデータベースでよく見られる多数の連結されたデータセットではなく、1つのデータセットに焦点が当てられている。 雑然としたデータとは、データの他の配置のことです。
整頓されたデータは、データセットを構成する標準的な方法を提供するため、分析者やコンピュータが必要な変数を簡単に抽出することができます。 教室のデータの異なるバージョンを比較してみてください。 混乱したバージョンでは、異なる変数を抽出するために異なる戦略を使用する必要があります。 これでは、分析が遅くなり、エラーが発生しやすくなります。 多くのデータ分析操作が、変数のすべての値を含むことを考えれば(すべての集約関数)、これらの値をシンプルで標準的な方法で抽出することがいかに重要かがわかるだろう。 tidy data は、Rのようなベクトル化されたプログラミング言語に特に適しています。 なぜなら、レイアウトによって、同じ観測結果からの異なる変数の値が常にペアになるからです。
変数とオブザベーションの順序は分析に影響しませんが、順序が良いと生の値をスキャンするのが容易になります。 変数を整理する1つの方法は、分析における役割によるものです。 データ収集の設計によって値が固定されているのか、それとも実験の過程で測定されているのか? 固定変数は、実験計画を記述し、事前に知られています。 コンピュータ科学者はしばしば固定変数を次元と呼び、統計学者は通常、ランダム変数の添え字で表します。 測定された変数は、研究で実際に測定するものです。固定変数が最初に来て、次に測定された変数が続き、それぞれ関連する変数が連続するように並べます。 そして、最初の変数で行を並べ、2番目以降の(固定)変数との関係を断ち切ることができる。これは、本稿のすべての表形式の表示で採用されている規則である。
実際のデータセットは、整頓されたデータの3つの教訓を、考えられるほぼすべての方法で破ることができますし、しばしばそうなります。 たまに、すぐに分析を始められるようなデータセットが手に入ることもありますが、それは例外であって、原則ではありません。 このセクションでは、整頓されていないデータの5つの最も一般的な問題と、その解決策を説明します。
列のヘッダが変数名ではなく値になっている。
1つの列に複数の変数が格納されている。
変数が行と列の両方に格納されている。
複数の種類の観測単位が同じテーブルに格納されている。
1つの観測ユニットが複数のテーブルに格納されている。
驚くべきことに、上記で明示的に説明されていないタイプのゴチャゴチャしたデータセットを含め、ほとんどのゴチャゴチャしたデータセットは、ピボット(長く、広く)とセパレートという小さなツールセットで整頓することができる。 以下のセクションでは、私が遭遇した実際のデータセットを使って各問題を説明し、それらを整頓する方法を示しています。
混乱したデータセットの一般的なタイプは、プレゼンテーション用に作成された表形式のデータで、変数が行と列の両方を構成し、列のヘッダーは変数名ではなく値になっています。 私はこの配置を厄介だと思っていますが、場合によっては非常に役に立つこともあります。 完全にクロスしたデザインを効率的に保存することができますし、必要な操作が行列操作として表現できる場合には、非常に効率的な計算を行うことができます。
次のコードは、この形式の典型的なデータセットのサブセットを示しています。このデータセットは、アメリカにおける所得と宗教の関係を調べたものです。 Pew Research Centerというアメリカのシンクタンクが作成した報告書からのものです。 Pew Research Centerは、宗教からインターネットまで、さまざまなトピックに対する態度についてのデータを収集しており、この形式のデータセットを含む報告書を多数作成しています。
relig_income#> # A tibble: 18 × 11
#> religion `<$10k` $10-2…¹ $20-3…² $30-4…³ $40-5…⁴ $50-7…⁵ $75-1…⁶ $100-…⁷
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Agnostic 27 34 60 81 76 137 122 109
#> 2 Atheist 12 27 37 52 35 70 73 59
#> 3 Buddhist 27 21 30 34 33 58 62 39
#> 4 Catholic 418 617 732 670 638 1116 949 792
#> 5 Don’t know/… 15 14 15 11 10 35 21 17
#> 6 Evangelical … 575 869 1064 982 881 1486 949 723
#> 7 Hindu 1 9 7 9 11 34 47 48
#> 8 Historically… 228 244 236 238 197 223 131 81
#> 9 Jehovah's Wi… 20 27 24 24 21 30 15 11
#> 10 Jewish 19 19 25 25 30 95 69 87
#> # … with 8 more rows, 2 more variables: `>150k` <dbl>,
#> # `Don't know/refused` <dbl>, and abbreviated variable names ¹`$10-20k`,
#> # ²`$20-30k`, ³`$30-40k`, ⁴`$40-50k`, ⁵`$50-75k`, ⁶`$75-100k`, ⁷`$100-150k`
このデータセットには、religion
, income
, frequency
という3つの変数があります。 これを整理するには、変数以外の列を2列のキーと値のペアに ピボット する必要があります。 この作業はよく、広いデータセットを長く(あるいは高く)すると表現されます。
変数をピボットする際には、新たに作成するキーバリュー列の名前を指定する必要があります。 ピボットする列(religionを除くすべての列)を定義した後、キーとなる列の名前が必要になります。 これは、列の見出しの値で定義される変数の名前です。 ここでは income
としています。2番目の引数は値の列の名前、frequency
です。
%>%
relig_income pivot_longer(-religion, names_to = "income", values_to = "frequency")
#> # A tibble: 180 × 3
#> religion income frequency
#> <chr> <chr> <dbl>
#> 1 Agnostic <$10k 27
#> 2 Agnostic $10-20k 34
#> 3 Agnostic $20-30k 60
#> 4 Agnostic $30-40k 81
#> 5 Agnostic $40-50k 76
#> 6 Agnostic $50-75k 137
#> 7 Agnostic $75-100k 122
#> 8 Agnostic $100-150k 109
#> 9 Agnostic >150k 84
#> 10 Agnostic Don't know/refused 96
#> # … with 170 more rows
この形式は、各列が変数を表し、各行が観測値(この場合は、「宗教」と「所得」の組み合わせに対応する人口統計学的単位)を表すので、整然としています。
この形式は、時間的に一定の間隔で観測値を記録する場合にも使用されます。例えば,下記のビルボードのデータセットは,ある曲がビルボードのトップ100に初めて入った日を記録しています。 このデータセットには、artist
, track
, date.entered
, rank
, week
という変数があります。 トップ100に入った後の各週のランクは、wk1
から wk75
までの75列に記録されています。 この保存形式は整然としたものではありませんが、データ入力には便利です。 そうしないと、各週の各曲がそれぞれの列を必要とし、タイトルやアーティストなどの曲のメタデータを繰り返す必要があるため、重複を減らすことができます。 これについては、multiple types で詳しく説明します。
billboard#> # A tibble: 317 × 79
#> artist track date.ent…¹ wk1 wk2 wk3 wk4 wk5 wk6 wk7 wk8 wk9
#> <chr> <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2 Pac Baby… 2000-02-26 87 82 72 77 87 94 99 NA NA
#> 2 2Ge+h… The … 2000-09-02 91 87 92 NA NA NA NA NA NA
#> 3 3 Doo… Kryp… 2000-04-08 81 70 68 67 66 57 54 53 51
#> 4 3 Doo… Loser 2000-10-21 76 76 72 69 67 65 55 59 62
#> 5 504 B… Wobb… 2000-04-15 57 34 25 17 17 31 36 49 53
#> 6 98^0 Give… 2000-08-19 51 39 34 26 26 19 2 2 3
#> 7 A*Tee… Danc… 2000-07-08 97 97 96 95 100 NA NA NA NA
#> 8 Aaliy… I Do… 2000-01-29 84 62 51 41 38 35 35 38 38
#> 9 Aaliy… Try … 2000-03-18 59 53 38 28 21 18 16 14 12
#> 10 Adams… Open… 2000-08-26 76 76 74 69 68 67 61 58 57
#> # … with 307 more rows, 67 more variables: 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>,
#> # wk37 <dbl>, wk38 <dbl>, wk39 <dbl>, wk40 <dbl>, wk41 <dbl>, wk42 <dbl>,
#> # wk43 <dbl>, wk44 <dbl>, wk45 <dbl>, wk46 <dbl>, wk47 <dbl>, wk48 <dbl>, …
このデータセットを整理するために,まずpivot_longer()
を使ってデータセットを長くします。 列を wk1
から wk76
に変換し、名前を表す新しい列 week
と、値を表す新しい値 rank
を作ります。
<- billboard %>%
billboard2 pivot_longer(
:wk76,
wk1names_to = "week",
values_to = "rank",
values_drop_na = TRUE
)
billboard2#> # A tibble: 5,307 × 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 2Ge+her The Hardest Part Of ... 2000-09-02 wk1 91
#> 9 2Ge+her The Hardest Part Of ... 2000-09-02 wk2 87
#> 10 2Ge+her The Hardest Part Of ... 2000-09-02 wk3 92
#> # … with 5,297 more rows
ここでは、values_drop_na = TRUE
を使って、ランク列から欠損値を削除しています。 このデータでは、欠損値は曲がチャートに入っていなかった週を表しているので、安全に取り除くことができます。
このケースでは、ちょっとしたクリーニングをして、week変数を数字に変換し、チャートの各週に対応する日付を算出するのもいいでしょう。
<- billboard2 %>%
billboard3 mutate(
week = as.integer(gsub("wk", "", week)),
date = as.Date(date.entered) + 7 * (week - 1),
date.entered = NULL
)
billboard3#> # A tibble: 5,307 × 5
#> artist track week rank date
#> <chr> <chr> <int> <dbl> <date>
#> 1 2 Pac Baby Don't Cry (Keep... 1 87 2000-02-26
#> 2 2 Pac Baby Don't Cry (Keep... 2 82 2000-03-04
#> 3 2 Pac Baby Don't Cry (Keep... 3 72 2000-03-11
#> 4 2 Pac Baby Don't Cry (Keep... 4 77 2000-03-18
#> 5 2 Pac Baby Don't Cry (Keep... 5 87 2000-03-25
#> 6 2 Pac Baby Don't Cry (Keep... 6 94 2000-04-01
#> 7 2 Pac Baby Don't Cry (Keep... 7 99 2000-04-08
#> 8 2Ge+her The Hardest Part Of ... 1 91 2000-09-02
#> 9 2Ge+her The Hardest Part Of ... 2 87 2000-09-09
#> 10 2Ge+her The Hardest Part Of ... 3 92 2000-09-16
#> # … with 5,297 more rows
最後に、データをソートするのは常に良いアイデアです。 アーティスト別、トラック別、週別などが考えられます。
%>% arrange(artist, track, week)
billboard3 #> # A tibble: 5,307 × 5
#> artist track week rank date
#> <chr> <chr> <int> <dbl> <date>
#> 1 2 Pac Baby Don't Cry (Keep... 1 87 2000-02-26
#> 2 2 Pac Baby Don't Cry (Keep... 2 82 2000-03-04
#> 3 2 Pac Baby Don't Cry (Keep... 3 72 2000-03-11
#> 4 2 Pac Baby Don't Cry (Keep... 4 77 2000-03-18
#> 5 2 Pac Baby Don't Cry (Keep... 5 87 2000-03-25
#> 6 2 Pac Baby Don't Cry (Keep... 6 94 2000-04-01
#> 7 2 Pac Baby Don't Cry (Keep... 7 99 2000-04-08
#> 8 2Ge+her The Hardest Part Of ... 1 91 2000-09-02
#> 9 2Ge+her The Hardest Part Of ... 2 87 2000-09-09
#> 10 2Ge+her The Hardest Part Of ... 3 92 2000-09-16
#> # … with 5,297 more rows
または、日付とランクで。
%>% arrange(date, rank)
billboard3 #> # A tibble: 5,307 × 5
#> artist track week rank date
#> <chr> <chr> <int> <dbl> <date>
#> 1 Lonestar Amazed 1 81 1999-06-05
#> 2 Lonestar Amazed 2 54 1999-06-12
#> 3 Lonestar Amazed 3 44 1999-06-19
#> 4 Lonestar Amazed 4 39 1999-06-26
#> 5 Lonestar Amazed 5 38 1999-07-03
#> 6 Lonestar Amazed 6 33 1999-07-10
#> 7 Lonestar Amazed 7 29 1999-07-17
#> 8 Amber Sexual 1 99 1999-07-17
#> 9 Lonestar Amazed 8 29 1999-07-24
#> 10 Amber Sexual 2 99 1999-07-24
#> # … with 5,297 more rows
列をピボットした後、キーとなる列が複数の基礎となる変数名の組み合わせになることがあります。 これは、以下の tb
(tuberculosis) データセットで起こります。 このデータセットは世界保健機関(World Health Organisation)からのもので、確認された結核の症例数を「国」、「年」、「人口グループ」ごとに記録しています。 人口統計グループは、「性別」(男性、女性)と「年齢」(0~14歳、15~25歳、25~34歳、35~44歳、45~54歳、55~64歳、不明)で分類されています。
<- as_tibble(read.csv("tb.csv", stringsAsFactors = FALSE))
tb
tb#> # A tibble: 5,769 × 22
#> iso2 year m04 m514 m014 m1524 m2534 m3544 m4554 m5564 m65 mu f04
#> <chr> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 AD 1989 NA NA NA NA NA NA NA NA NA NA NA
#> 2 AD 1990 NA NA NA NA NA NA NA NA NA NA NA
#> 3 AD 1991 NA NA NA NA NA NA NA NA NA NA NA
#> 4 AD 1992 NA NA NA NA NA NA NA NA NA NA NA
#> 5 AD 1993 NA NA NA NA NA NA NA NA NA NA NA
#> 6 AD 1994 NA NA NA NA NA NA NA NA NA NA NA
#> 7 AD 1996 NA NA 0 0 0 4 1 0 0 NA NA
#> 8 AD 1997 NA NA 0 0 1 2 2 1 6 NA NA
#> 9 AD 1998 NA NA 0 0 0 1 0 0 0 NA NA
#> 10 AD 1999 NA NA 0 0 0 1 1 0 0 NA NA
#> # … with 5,759 more rows, and 9 more variables: f514 <int>, f014 <int>,
#> # f1524 <int>, f2534 <int>, f3544 <int>, f4554 <int>, f5564 <int>, f65 <int>,
#> # fu <int>
まず、pivot_longer()
を使って、変数以外の列を集めます。
<- tb %>%
tb2 pivot_longer(
!c(iso2, year),
names_to = "demo",
values_to = "n",
values_drop_na = TRUE
)
tb2#> # A tibble: 35,750 × 4
#> iso2 year demo n
#> <chr> <int> <chr> <int>
#> 1 AD 1996 m014 0
#> 2 AD 1996 m1524 0
#> 3 AD 1996 m2534 0
#> 4 AD 1996 m3544 4
#> 5 AD 1996 m4554 1
#> 6 AD 1996 m5564 0
#> 7 AD 1996 m65 0
#> 8 AD 1996 f014 0
#> 9 AD 1996 f1524 1
#> 10 AD 1996 f2534 1
#> # … with 35,740 more rows
この形式のカラムヘッダは,英数字以外の文字(例:.
, -
, _
, :
)で区切られていたり、このデータセットのように固定幅の形式になっていたりします。 separate()
を使うと、複合変数を個々の変数に簡単に分割することができます。 分割するための正規表現(デフォルトでは英数字以外の列で分割されます)、または文字位置のベクトルを渡すことができます。 この例では、最初の文字の後で分割したいと思います。
<- tb2 %>%
tb3 separate(demo, c("sex", "age"), 1)
tb3#> # A tibble: 35,750 × 5
#> iso2 year sex age n
#> <chr> <int> <chr> <chr> <int>
#> 1 AD 1996 m 014 0
#> 2 AD 1996 m 1524 0
#> 3 AD 1996 m 2534 0
#> 4 AD 1996 m 3544 4
#> 5 AD 1996 m 4554 1
#> 6 AD 1996 m 5564 0
#> 7 AD 1996 m 65 0
#> 8 AD 1996 f 014 0
#> 9 AD 1996 f 1524 1
#> 10 AD 1996 f 2534 1
#> # … with 35,740 more rows
このような形で値を保存することで、元のデータの問題点を解決することができます。 数ではなく率を比較したいので、母集団を知る必要があります。 元の形式では、人口変数を追加する簡単な方法がありません。 別のテーブルに保存しなければならないので、母集団とカウントを正しく一致させるのは難しいのです。 tidy 形式では、人口と率の変数を追加するのは、単なる追加列なので簡単です。
この場合、names_to
に複数のカラム名を与え、さらに names_pattern
にグループ化された正規表現を与えることで、ワンステップで変換を行うこともできます。
%>% pivot_longer(
tb !c(iso2, year),
names_to = c("sex", "age"),
names_pattern = "(.)(.+)",
values_to = "n",
values_drop_na = TRUE
)#> # A tibble: 35,750 × 5
#> iso2 year sex age n
#> <chr> <int> <chr> <chr> <int>
#> 1 AD 1996 m 014 0
#> 2 AD 1996 m 1524 0
#> 3 AD 1996 m 2534 0
#> 4 AD 1996 m 3544 4
#> 5 AD 1996 m 4554 1
#> 6 AD 1996 m 5564 0
#> 7 AD 1996 m 65 0
#> 8 AD 1996 f 014 0
#> 9 AD 1996 f 1524 1
#> 10 AD 1996 f 2534 1
#> # … with 35,740 more rows
変数が行と列の両方に格納されている場合、最も複雑な形でデータが混乱します。 以下のコードは、Global Historical Climatology Network から、2010年の5ヶ月間、メキシコの1つのウェザーステーション(MX17004)の日ごとの気象データを読み込みます。
<- as_tibble(read.csv("weather.csv", stringsAsFactors = FALSE))
weather
weather#> # A tibble: 22 × 35
#> id year month element d1 d2 d3 d4 d5 d6 d7 d8
#> <chr> <int> <int> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 MX17004 2010 1 tmax NA NA NA NA NA NA NA NA
#> 2 MX17004 2010 1 tmin NA NA NA NA NA NA NA NA
#> 3 MX17004 2010 2 tmax NA 27.3 24.1 NA NA NA NA NA
#> 4 MX17004 2010 2 tmin NA 14.4 14.4 NA NA NA NA NA
#> 5 MX17004 2010 3 tmax NA NA NA NA 32.1 NA NA NA
#> 6 MX17004 2010 3 tmin NA NA NA NA 14.2 NA NA NA
#> 7 MX17004 2010 4 tmax NA NA NA NA NA NA NA NA
#> 8 MX17004 2010 4 tmin NA NA NA NA NA NA NA NA
#> 9 MX17004 2010 5 tmax NA NA NA NA NA NA NA NA
#> 10 MX17004 2010 5 tmin NA NA NA NA NA NA NA NA
#> # … with 12 more rows, and 23 more variables: d9 <lgl>, d10 <dbl>, d11 <dbl>,
#> # d12 <lgl>, d13 <dbl>, d14 <dbl>, d15 <dbl>, d16 <dbl>, d17 <dbl>,
#> # d18 <lgl>, d19 <lgl>, d20 <lgl>, d21 <lgl>, d22 <lgl>, d23 <dbl>,
#> # d24 <lgl>, d25 <dbl>, d26 <dbl>, d27 <dbl>, d28 <dbl>, d29 <dbl>,
#> # d30 <dbl>, d31 <dbl>
これは、個々の列(id
, year
, month
)に変数を持ち、列(day
, d1-d31)と行(tmin
, tmax
)に分散しています(最低気温と最高気温)。 31日に満たない月は、その月の最終日が構造的に欠落しています。
このデータセットを整理するために、まず pivot_longer を使って日の列を集めます。
<- weather %>%
weather2 pivot_longer(
:d31,
d1names_to = "day",
values_to = "value",
values_drop_na = TRUE
)
weather2#> # A tibble: 66 × 6
#> id year month element day value
#> <chr> <int> <int> <chr> <chr> <dbl>
#> 1 MX17004 2010 1 tmax d30 27.8
#> 2 MX17004 2010 1 tmin d30 14.5
#> 3 MX17004 2010 2 tmax d2 27.3
#> 4 MX17004 2010 2 tmax d3 24.1
#> 5 MX17004 2010 2 tmax d11 29.7
#> 6 MX17004 2010 2 tmax d23 29.9
#> 7 MX17004 2010 2 tmin d2 14.4
#> 8 MX17004 2010 2 tmin d3 14.4
#> 9 MX17004 2010 2 tmin d11 13.4
#> 10 MX17004 2010 2 tmin d23 10.7
#> # … with 56 more rows
表示のために、私は欠損値を削除して、明示的ではなく暗黙的にしました。 これは、各月の日数がわかっているので、明示的な欠損値を簡単に再構成できるからです。
また、ちょっとしたクリーニングも行います。
<- weather2 %>%
weather3 mutate(day = as.integer(gsub("d", "", day))) %>%
select(id, year, month, day, element, value)
weather3#> # A tibble: 66 × 6
#> id year month day element value
#> <chr> <int> <int> <int> <chr> <dbl>
#> 1 MX17004 2010 1 30 tmax 27.8
#> 2 MX17004 2010 1 30 tmin 14.5
#> 3 MX17004 2010 2 2 tmax 27.3
#> 4 MX17004 2010 2 3 tmax 24.1
#> 5 MX17004 2010 2 11 tmax 29.7
#> 6 MX17004 2010 2 23 tmax 29.9
#> 7 MX17004 2010 2 2 tmin 14.4
#> 8 MX17004 2010 2 3 tmin 14.4
#> 9 MX17004 2010 2 11 tmin 13.4
#> 10 MX17004 2010 2 23 tmin 10.7
#> # … with 56 more rows
このデータセットはほとんど整頓されていますが、element
列は変数ではなく、変数の名前を格納しています。 この例では、他の気象変数である prcp
(降水量)と snow
(降雪量)は表示されていません)。 これを解決するには,データを広げる必要があります。 pivot_wider()
は pivot_longer()
の逆で、element
と value
を複数の列に渡ってピボットします。
%>%
weather3 pivot_wider(names_from = element, values_from = value)
#> # A tibble: 33 × 6
#> id year month day tmax tmin
#> <chr> <int> <int> <int> <dbl> <dbl>
#> 1 MX17004 2010 1 30 27.8 14.5
#> 2 MX17004 2010 2 2 27.3 14.4
#> 3 MX17004 2010 2 3 24.1 14.4
#> 4 MX17004 2010 2 11 29.7 13.4
#> 5 MX17004 2010 2 23 29.9 10.7
#> 6 MX17004 2010 3 5 32.1 14.2
#> 7 MX17004 2010 3 10 34.5 16.8
#> 8 MX17004 2010 3 16 31.1 17.6
#> 9 MX17004 2010 4 27 36.3 16.7
#> 10 MX17004 2010 5 27 33.2 18.2
#> # … with 23 more rows
このフォームは整然としています。 各列には1つの変数があり、各行は1日を表しています。
データセットには、複数のレベル、異なる型の観測単位で収集された値が含まれることがよくあります。 整頓の際には、各タイプの観測単位をそれぞれのテーブルに格納する必要があります。 これは、データベースの正規化の考え方と密接に関連しており、各ファクトが1つの場所でのみ表現されます。 そうしないと不整合が生じる可能性があるため、重要です。
ビルボードのデータセットには、実際には、曲と各週での順位という2種類の観測単位が含まれています。 このことは、曲に関する事実が重複していることからもわかります。例えば、artist
は何度も繰り返されます。
このデータセットを2つの部分に分解する必要があります。 すなわち、 artist
と song name
を格納する曲データセットと、各 week
における song
の rank
を与えるランキングデータセットです。 まず、 “song” のデータセットを抽出します。
<- billboard3 %>%
song distinct(artist, track) %>%
mutate(song_id = row_number())
song#> # A tibble: 317 × 3
#> artist track song_id
#> <chr> <chr> <int>
#> 1 2 Pac Baby Don't Cry (Keep... 1
#> 2 2Ge+her The Hardest Part Of ... 2
#> 3 3 Doors Down Kryptonite 3
#> 4 3 Doors Down Loser 4
#> 5 504 Boyz Wobble Wobble 5
#> 6 98^0 Give Me Just One Nig... 6
#> 7 A*Teens Dancing Queen 7
#> 8 Aaliyah I Don't Wanna 8
#> 9 Aaliyah Try Again 9
#> 10 Adams, Yolanda Open My Heart 10
#> # … with 307 more rows
そのデータを使って、繰り返される曲の情報を曲の詳細(一意の曲ID)へのポインタに置き換えることで、rank
データセットを作成します。
<- billboard3 %>%
rank left_join(song, c("artist", "track")) %>%
select(song_id, date, week, rank)
rank#> # A tibble: 5,307 × 4
#> song_id date week rank
#> <int> <date> <int> <dbl>
#> 1 1 2000-02-26 1 87
#> 2 1 2000-03-04 2 82
#> 3 1 2000-03-11 3 72
#> 4 1 2000-03-18 4 77
#> 5 1 2000-03-25 5 87
#> 6 1 2000-04-01 6 94
#> 7 1 2000-04-08 7 99
#> 8 2 2000-09-02 1 91
#> 9 2 2000-09-09 2 87
#> 10 2 2000-09-16 3 92
#> # … with 5,297 more rows
また、week
データセットを用意して、その週の背景情報を記録することもできます。曲の総売り上げ数や、似たような「人口統計」情報などです。
正規化は、データを整理して不整合をなくすのに便利です。 しかし、リレーショナルデータを直接扱えるデータ分析ツールはほとんどないので、 分析には通常、非正規化やデータセットを1つのテーブルに戻す作業も必要になります。
1種類の観測ユニットに関するデータ値が、複数のテーブルやファイルに分散していることもよくあります。これらのテーブルやファイルは、他の変数によって分割されていることが多く、それぞれが1つの年、人、場所を表しています。個々のレコードのフォーマットが一貫していれば、この問題は簡単に解決できます。
ファイルをテーブルのリストに読み込む。
各テーブルに、元のファイル名を記録する新しい列を追加します。 (ファイル名はしばしば重要な変数の値である)。
すべてのテーブルを1つのテーブルにまとめる。
以下のコードでは,ディレクトリ(data/
)内のファイル名が正規表現(.csv
で終わる)にマッチするベクトルを生成します。 次に,ベクトルの各要素にファイル名を付けます。 これは、次のステップで名前を保存するためで,最終的なデータフレームの各行にソースのラベルが付けられるようにします。 最後に、map_dfr()
が各パスをループし、csvファイルを読み込んで、結果を1つのデータフレームにまとめます.
library(purrr)
<- dir("data", pattern = "\\.csv$", full.names = TRUE)
paths names(paths) <- basename(paths)
map_dfr(paths, read.csv, stringsAsFactors = FALSE, .id = "filename")
一つのテーブルができたら、必要に応じて追加の整理を行うことができます。 この種のクリーニングの例としては、https://github.com/hadley/data-baby-namesにあるように、 米国社会保障庁から提供された129の年ごとの赤ちゃんの名前のテーブルを取り込んで、1つのファイルにまとめています。
さらに複雑なのは、データセットの構造が時間とともに変化する場合である。 例えば、データセットに異なる変数が含まれていたり、同じ変数でも名前が違っていたり、ファイル形式が違っていたり、欠損値の扱いが違っていたりします。 このような場合には、各ファイルを個別に(運が良ければ小グループで)整頓し、整頓した後に結合する必要があるかもしれません。 この種の整頓の例をhttps://github.com/hadley/data-fuel-economyに示します。 これは、1978年から2008年までの5万台以上の自動車のepa燃費データを整頓したものです。 生データはオンラインで入手できますが、各年が別々のファイルに保存されており、4つの主要なフォーマットと多くのマイナーなバリエーションがあるため、このデータセットを整頓するのはかなり困難です。