横持ちと縦持ち: 同じデータ、2 つの形
変形に関するすべての問いは 1 つの区別に帰着するので、実際に 見て みましょう。2 都市の四半期売上という小さなデータセットを、両方の形で組み立てます。
情報は同一で、幾何学的な形が異なります。横持ち(wide) は都市ごとに 1 行、四半期ごとに 1 列を持ち、四半期は 列名 の中に住んでいます。縦持ち(long) は観測(都市 × 四半期)ごとに 1 行を持ち、四半期は普通の列に降格され、すべての測定値が 1 つの sales 列に入ります。
どちらの形が「正しい」というわけではありません。仕える相手が違うのです。横持ちは人が読み、表計算ソフトが書き出すものです。コンパクトで一目で比較できます。縦持ちはツールが消費するものです。両者の変換を ピボット(あるいはリシェイプ。同じことです)と呼びます。
分析では縦持ちが勝つ理由
横持ちが「四半期」という概念に何をしたか見てください。1 つの変数を列 名 に切り刻んでしまいました。列名では何も計算できません。縦持ちでは quarter が再びデータになり、グループ分析の道具箱がまるごと使えるようになります。
aggregate(sales ~ quarter, ...)やgroup_by(quarter)— 横持ちでは不可能です。各四半期が別々の列であり、手作業で扱うことになります(グループ集計 はすべて縦持ちのデータを前提にしています)。- ggplot2 は 列 を審美的属性に対応付けます:
aes(x = quarter, y = sales, color = city)にはその 3 つの列が存在する必要があります。横持ちのデータの作図は絶えず戦いですが、縦持ちのデータなら 1 行です。 - Q3 を追加することは 行 を追加することであり、スキーマの変更はありません。横持ちでは新しい列が生え、下流のすべての段階がそれを知らなければなりません。
実践的な原則はこうです。保存と分析は縦持ちで、提示は横持ちで。 だからこそ以下の 2 つの変換が非常によく使われます。データは表計算ソフトから横持ちで届き、分析のために縦にされ、レポートの最終的な表のために再び横にされるのです。
横持ちから縦持ちへ: pivot_longer()
tidyr パッケージ(tidyverse における dplyr の兄弟)が現代的な変形を担います。ここでのスニペットは静的です — サンドボックスは base R しか実行しません。縦にする作業には 3 つの判断があり、それぞれに引数が 1 つ対応します。
library(tidyr)
long <- pivot_longer(wide,
cols = c(Q1, Q2), # which columns to stack
names_to = "quarter", # new column that receives the old NAMES
values_to = "sales" # new column that receives the cell VALUES
)
順に見ていきましょう。cols は解体される列を指定します(Q1:Q2 の範囲記法や starts_with("Q") のようなヘルパーも使えます — 20 列もあるときに便利です)。選ばれた各セルが 1 行になり、元の列は quarter に、数値そのものは sales に入ります。cols に列挙 されなかった 列(ここでは city)は識別子として扱われ、行に沿って繰り返されます。結果は上で表示した long のフレームとまったく同じです。
縦持ちから横持ちへ: pivot_wider()
逆方向の旅には 2 つの判断があります。新しい列名がどこから来るのか、そして何がそれを埋めるのかです。
wide <- pivot_wider(long,
names_from = quarter, # distinct values here become new columns
values_from = sales # these values fill the cells
)
quarter の各異なる値(Q1, Q2)が列になり、各セルはその都市と四半期に一致する行の sales の値で埋められます。残りの列(city)は行の識別子として働き、異なる組み合わせごとに 1 つの出力行になります。ある四半期が欠けている都市は、そのセルが NA になります(values_fill 引数で別の値に置き換えられます。たとえば件数データなら values_fill = 0)。
古いチュートリアルでは前の世代にも出会うでしょう。spread() は pivot_wider()、gather() は pivot_longer() に相当します — tidyr 1.0 以降は後継に置き換えられました。今も動きますが、見分けられる以上に学ぶ価値はありません。
base R には reshape() がある — 率直な警告
base R はパッケージなしでこれを行えます。reshape() — 自身のドキュメントが歴史的に謝罪していたほど有名に分かりにくい関数です。縦断研究の語彙(idvar, timevar, direction)を中心に設計されており、引数名が日常のデータにぎこちなく対応し、誰もが使うたびに忘れます。それでも動きます。
出力の名前に注目してください。sales.Q1, sales.Q2 — 値の列の名前がそれぞれに融合しています。急場しのぎ、依存関係ゼロの環境、古いコードで出会ったときには構いません。しかし年に 2 回以上データを変形するなら、install.packages("tidyr") が率直な推奨です — これは、base R の道具が節約する依存関係よりも本当に高くつく、唯一のデータ整形作業です。
横持ち化におけるキー重複の落とし穴
横持ち化は、識別子と名前の各組み合わせが 1 つの 値を特定することを前提とします。Lima に Q1 の行が 2 つあったら、単一の Q1 セルにはどちらの数値が入るのでしょうか。pivot_wider() は推測を拒みます。警告(values are not uniquely identified)を出し、セルに リスト列 を入れます — リストが入れ子になったデータフレームで、次に行う処理を壊します。
その警告を見たら、まず values_fn という脱出口に手を伸ばすのではなく、なぜ 重複が存在するのかを問いましょう。たいていはデータが思っていたより細かい粒度である(四半期ではなく日次の行 — なら先に集計してから横持ちにします)か、上流のジョインが行を重複させたかです。values_fn = mean(または sum)が正当な対処になるのは、重複をどの集計でまとめるべきかを口に出して言えるようになってからです。ここでは reshape() の方が悪質です。最初の重複を黙って残して残りを捨て、簡単に見落とせる警告しか出しません。
この記事のまとめ
- 横持ち: 変数が列名に隠れている、読むための形。縦持ち: 観測ごとに 1 行、分析と ggplot2 のための形。
- 保存と分析は縦持ちで、提示は横持ちで。
pivot_longer(cols, names_to, values_to)は列を行に積み重ねます。列挙しなかった列は繰り返される識別子になります。pivot_wider(names_from, values_from)は行を列に展開します。欠けた部分はNAで埋まります。spread()/gather()は古い名前です。base のreshape()は動きますが、有名にぎこちないものです。- 識別子と名前の対が重複していると横持ち化は曖昧になります — 警告を黙らせるのではなく、先に集計しましょう。
次は ファイルからの実データの読み込み — read.csv() とその鋭い刃です。
よくある質問
R における横持ち(wide)と縦持ち(long)のデータの違いは何ですか?
同じデータの違う形です。横持ちは 1 つの変数を列にまたがって展開し(たとえば四半期ごとに 1 列)、対象ごとに 1 行になります — 人が読むための形です。縦持ちはそれを行に積み重ね、観測ごとに 1 行、名前の列と値の列を持ちます — グループ分析と ggplot2 のための形です。
R で横持ちのデータを縦持ちに変換するにはどうすればよいですか?
tidyr の pivot_longer(df, cols = Q1:Q4, names_to = "quarter", values_to = "sales") が選んだ列を 2 つの新しい列に積み重ねます。元の列名が names_to の列に、セルの値が values_to の列に入ります。
R で縦持ちのデータを横持ちに変換するにはどうすればよいですか?
tidyr の pivot_wider(df, names_from = quarter, values_from = sales) が行を列に展開します。names_from の各異なる値が列になり、対応する values_from の項目で埋められます。組み合わせが存在しない箇所は NA になります。
R で spread と gather の代わりになったのは何ですか?
tidyr 1.0(2019 年)で pivot_wider() が spread() を、pivot_longer() が gather() を置き換えました。古い関数は今でも動き、古いチュートリアルでは目にしますが、新しいコードはすべて、引数がより明快で機能も多い pivot_* の組を使うべきです。