この用語と一緒に検索・参照されやすいタグ。 関連ページに飛ぶときの手がかりにも使えます。
「data long wide」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「data long wide」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「data long wide の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
データの並べ方の2つのパターンのことです。
分析や表示をスムーズにするために使います。
スマホの連絡先リストのような表を想像してください。
まずは結論から短くまとめました。
縦持ち(long)と横持ち(wide)は、 同じ情報を異なる形に並べたテーブルの 2 つの正準形態。
melt(横→縦)/ pivot(縦→横)ここまでが要点です。 ただし実際に使う前に、 このページの「⚠️ よくある落とし穴」で挙げた 「型混在」の縦持ち/pivot の重複値エラー/「人間用と機械用を混同」 には必ず目を通してください。 つまずくのは知識が無いときより、 知ってはいたが確認を飛ばしたときです。
🍰 まずはやさしく
データの形を使い分ける場面の話です。
ソフトに合わせて形を変えるために使います。
部活の成績をグラフにする時に必要になります。
どんな場面でこの考え方が役立つか説明します。
SSDSE-B-2026 は典型的な横持ち(年×県の各行に 100 列の指標)。 一方、 ggplot や seaborn は縦持ちを前提。 「変換できる」ことが分析の前提条件。
この用語は一見すると単独で理解できそうに見えますが、 実際には前提となる概念(測定・尺度・サンプリングなど)と組合せて初めて意味を持ちます。 「定義を覚える」より「どんな問いに答える道具なのか」を捉えるのが効率的です。
🍰 まずはやさしく
データの見た目をイメージで捉えることです。
人間と機械のどちらが読みやすいか分けるためです。
買い物リストを縦に書くか横に書くかの違いです。
直感的に理解するための例を挙げます。
「縦持ち・横持ち」を最初に学ぶときは、 厳密な定義よりイメージを優先しましょう。 以下は具体例・比喩を用いた直感的理解の入口です。
エクセルでデータを管理するとき、 多くの人は「行 = 都道府県」「列 = 2018年, 2019年, …」と 横持ち(wide) で書きがちです。 一方、 統計ソフトや BI ツールは 縦持ち(long)、 つまり「行 = 都道府県 × 年 × 指標」を 1 行ずつ並べる形を好みます。 内容は同じでも形が違うと、 集計・可視化・統計モデルの当てはめやすさが大きく変わる ── これが long / wide 変換の本質です。
SSDSE-B-2026 は もともと wide 形式 です(1 行 = 年度 + 都道府県、 列 = 各指標)。 これを「年 × 都道府県」のクロス表に変換すると もう一段違う wide、 「年・指標・値」を 1 行ずつ並べると long になります。 同じ情報なのに見た目は別物。
この「年度 × 都道府県 × 多数の指標」が wide 形式。 ある 1 つの指標(例: A1101 総人口)に注目すると、 これを (行=都道府県, 列=年度) のクロス表に変えるのが「wide-to-wider pivot」、 あるいは (行=年度×都道府県×指標, 列=値) の long に変えるのが「tidy 形式」への変換です。
🍰 まずはやさしく
データの並べ方のきまりのことです。
間違いなく分析を行うために使います。
テストの点数表を正しく作るルールのようなものです。
縦持ちにするための3つの条件を学びます。
直感の次は、 厳密な定義を確認します。 数式は言語の一種で、 一度書き慣れれば「言葉より速く伝えられる」便利な道具。 慣れていない方は、 各記号が何を表すかを「🔬 数式を言葉で読み解く」で 1 つずつ確認してください。
Hadley Wickham(pandas / tidyverse の設計思想を提唱)は「tidy data」を以下の 3 条件で定義しました。 これが long 形式の理論的根拠です。
wide → long は pd.melt または df.stack()。 long → wide は pd.pivot_table または df.unstack()。 列名と行名(インデックス)の 役割の入れ替え がこの変換の本質。
id_vars=['Code', 'Prefecture', 'SSDSE-B-2026']数式を眺めるだけでは身につかないので、 各記号がどんな役割を担っているかを言葉で押さえます。 「数式を音読する習慣」がつくと、 論文や教科書を読むスピードが体感で 2 倍ほど上がります。
数式だけでは「実感」が湧きにくいので、 具体的な数値で 1 度手計算してみると理解が定着します。 以下の例は、 本サイトで扱う SSDSE-B-2026 や公開教材に近い形式で用意しました。
同じ情報の縦/横表現:
| 横持ち(wide) |
|---|
| 県=東京, 人口=14000000, 高齢化率=23.0%, 死亡率=11.0‰ |
| 縦持ち(long) |
|---|
| 県=東京, 指標=人口, 値=14000000 |
| 県=東京, 指標=高齢化率, 値=23.0 |
| 県=東京, 指標=死亡率, 値=11.0 |
手計算で得た値と、 後述の Python 実装で算出した値が一致することを確認すると、 「数式とコードの対応関係」がクリアに見えるようになります。
同じ「北海道の総人口」を 3 つの形式で並べてみます。 ぱっと見の印象がどう変わるかを体感してください。
→ 「年度ごとに 1 行」「列 = 指標」。 SSDSE-B-2026 のデフォルト形式。 多変量回帰には便利。
→ 「指標名」「値」が分離。 ggplot / seaborn / Tableau での可視化、 SQL の GROUP BY と相性最高。 行数は元の (指標数 × 年数 × 県数) 倍に増える。
→ 「年が列」になり、 ヒートマップ表示や年次変化の比較に最適。 ただし変数の数が固定の年次に縛られるため、 新年度追加時に列追加が必要。
合成データで 5 顧客 × 4 月の売上を long と wide で表示し、 行数を比較する。
| 顧客 | 1月 | 2月 | 3月 | 4月 |
|---|---|---|---|---|
| c1 | 10 | 15 | 12 | 20 |
| c2 | 5 | 8 | 6 | 9 |
1 2 3 4 5 6 7 8 9 10 11 | import pandas as pd wide = pd.DataFrame({ '顧客':['c1','c2','c3','c4','c5'], '1月':[10,5,8,12,7], '2月':[15,8,9,14,10], '3月':[12,6,7,11,8], '4月':[20,9,10,15,12], }) long = wide.melt(id_vars='顧客', var_name='月', value_name='売上') print(f"wide 形状: {wide.shape}") print(f"long 形状: {long.shape}") |
💬 手計算 (Step 2) 20 行と Python 出力が完全一致。
公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで動作させます。 ファイルパス(data/raw/SSDSE-B-2026.csv)は自分の環境に合わせて変更してください。 まずはこのまま動かすことが理解の最短ルートです。
1 2 3 4 5 6 7 8 9 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # 横→縦 long = df.melt(id_vars=['SSDSE-B-2026','Code','Prefecture'], var_name='indicator', value_name='value') print(long.head()) # 縦→横 wide = long.pivot_table(index=['SSDSE-B-2026','Code','Prefecture'], columns='indicator', values='value').reset_index() |
▶ 実行 を押せばこのページの中でそのまま動きます(ライブラリもデータも同梱済みで、 準備は要りません)。 手元の Python に移して動かすときは pip install matplotlib pandas scikit-learn seaborn が必要です。 読んでいるデータは data/raw/SSDSE-B-2026.csv。 日本語を含むので encoding='cp932' の指定を落とさないでください。
本サイトの全コードは 論文一覧ページ から実例として確認できます。 自分のデータで試したい場合は、 列名・欠損記号・単位の違いだけ調整すれば、 ほぼそのまま流用できます。
「縦持ち・横持ち」を初めて使う方向けに、 ハンズオン的な実行手順を整理します。 上の Python 実装と組み合わせて、 1 度自分の手でなぞってみることを強く推奨します。
data/raw/ に配置(または自分のデータを用意)。 列名と単位を確認。df.head()、 df.describe()、 df.isna().sum() で全体像を把握。 ここで欠損や外れ値の見当を付ける。この 8 ステップを 1 度回すと、 「用語を読んで分かった気になる」段階から「実際に使える」段階に進めます。 知識は身体で覚えるのが結局のところ最速です。
このコードでやること:SSDSE-B-2026 の wide データから、 「年度・Code・Prefecture」を識別子に保持し、 総人口・高齢者人口・出生数の 3 指標を long 形式に変換する。
📥 入力:SSDSE-B-2026.csv(564 行 × 112 列)。 必要な 6 列に絞ってから melt。
1 2 3 4 5 6 7 8 9 10 11 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) sub = df[['SSDSE-B-2026', 'Code', 'Prefecture', 'A1101', 'A1303', 'A4101']].rename( columns={'SSDSE-B-2026': '年度', 'A1101': '総人口', 'A1303': '高齢者人口', 'A4101': '出生数'}) df_long = sub.melt(id_vars=['年度', 'Code', 'Prefecture'], var_name='指標', value_name='値') print(f'wide shape: {sub.shape} long shape: {df_long.shape}') print(df_long.head(8)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:wide の 564×6 が long で 1692×5 に。 行数が 3 倍(指標数 3)になり、 列数が 1 つ減った(指標と値が 2 列に分割されたため)。 値列に異なる指標の数値が縦に並ぶ ── これを groupby('指標').mean() 等で扱うと指標横断の集計が一発で書ける。
このコードでやること:先ほどの sub(wide)から、 「行 = 都道府県、 列 = 年度、 値 = 総人口」のクロス表を作る。 ヒートマップ表示や時系列分析の前処理として定番。
📥 入力:先ほどの sub(564×6)。 pivot_table の index に Prefecture、 columns に年度、 values に総人口を指定。
1 2 3 4 5 6 7 8 | wide = sub.pivot_table(index=['Code', 'Prefecture'], columns='年度', values='総人口') print(f'pivot shape: {wide.shape}') print(wide.head(5)) # 年次変化率(前年比)の計算が 1 行で済む growth = (wide[2023] / wide[2012] - 1) * 100 print('2012→2023 人口変化率 (%) Top5 増加:\n', growth.nlargest(5).round(2)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:1692×5 の long が、 47×12 の年次クロス表に変換された。 2023 年と 2012 年の比較が「wide[2023] / wide[2012]」と 1 行で書ける ── これが wide 形式の威力。 増加率トップは東京都 +6.44%、 次いで沖縄県 +4.04%・神奈川県 +1.75%・埼玉県 +1.59%・千葉県 +0.92% ── 増加は大都市圏と沖縄に限られ、 大半の県は人口減少。 long のままだと shift/lag が面倒だが、 wide なら列同士の演算で済む。
このコードでやること:long 形式の利点を活かし、 groupby('指標').agg([...]) で 3 指標すべての年次推移統計を一発で計算する。
📥 入力:先ほどの df_long(1692×5、 指標列に「総人口」「高齢者人口」「出生数」)。
1 2 3 4 5 6 7 | # 年度・指標別の 47 県平均 summary = df_long.groupby(['年度', '指標'])['値'].agg(['mean', 'sum']).round(0) print('== 2023 年の 47 県平均・総和 ==') print(summary.loc[2023]) print('== 北海道の 12 年間平均・最大・最小 ==') print(df_long[df_long['Prefecture'] == '北海道'].groupby('指標')['値'].agg(['mean', 'max', 'min']).round(0)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:2023 年の 47 県合計総人口 ≒ 1 億 2435 万人、 高齢者人口 ≒ 3623 万人 → 高齢化率 29.1%。 北海道の出生数は 12 年間で 38,686 → 24,430 と 約 37% 減少、 一方総人口は 7% 減にとどまる ── 出生数の減少速度が圧倒的に速いことが long の集計で見える。 long 形式は「指標横断のクロス集計」を groupby 一行で書けるのが強み。
このコードでやること:「人口_2012, 人口_2013, ...」のように 列名にメタ情報が埋まった wide データを、 pd.wide_to_long で long に変換する。 SSDSE のような列名規則化されたデータには非常に強力。
📥 入力:先ほどの wide pivot を MultiIndex で複数指標化したもの。 列名が「総人口_2012」「高齢者人口_2012」のように prefix_year 形式。
1 2 3 4 5 6 7 8 9 10 | # まず「総人口_2012」のような prefix_year 列名の wide を作る wide2 = sub.pivot_table(index=['Code', 'Prefecture'], columns='年度', values=['総人口', '高齢者人口']) wide2.columns = [f'{a}_{b}' for a, b in wide2.columns] wide2 = wide2.reset_index() long2 = pd.wide_to_long(wide2, stubnames=['総人口', '高齢者人口'], i=['Code', 'Prefecture'], j='年度', sep='_').reset_index() print(f'wide2 shape: {wide2.shape} long2 shape: {long2.shape}') print(long2.head()) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:47×26 列(識別子 2 + 指標 2 × 12 年)の wide が、 564×5 列(識別子 2 + 年度 + 指標 2)の 「半 long」 になった。 完全 long と違い、 指標は 列として残る。 これが SSDSE のような縦軸(時間)に沿った分析に最適な形式。 完全 long にしたいなら、 続けて melt を 1 回かければよい。
| 関数 | 方向 | 使う場面 |
|---|---|---|
pd.melt(df) | wide → long | 最汎用。 列名を 1 つの「指標」列に集約 |
df.stack() | wide → long | MultiIndex 前提、 縦長 Series を返す |
pd.pivot_table | long → wide | 集約関数を指定可能、 重複に強い |
df.unstack() | long → wide | MultiIndex の階層を列に展開 |
pd.wide_to_long | prefix_year → long | 列名にメタ情報が埋まったデータに最適 |
long と wide のどちらが 可視化 に向いているかは、 図の種類で変わります。 seaborn / ggplot は long を好み、 matplotlib の imshow や Excel風ヒートマップは wide が直接使える。
このコードでやること:先ほどの df_long から 5 県(北海道・東京都・大阪府・沖縄県・秋田県)の総人口推移を sns.lineplot で 1 行描画する。 また同じデータを wide にして imshow でヒートマップ。
📥 入力:df_long(1692×5)から「指標==総人口」「Prefecture in 5 県」に絞った 60 行。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import seaborn as sns import matplotlib.pyplot as plt prefs = ['北海道', '東京都', '大阪府', '沖縄県', '秋田県'] d5 = df_long[(df_long['指標']=='総人口') & (df_long['Prefecture'].isin(prefs))] # long で折れ線(hue で自動色分け、 1 行で済む) sns.lineplot(data=d5, x='年度', y='値', hue='Prefecture') plt.title('5県の総人口推移'); plt.savefig('line.png'); plt.close() # wide にしてヒートマップ heat = d5.pivot_table(index='Prefecture', columns='年度', values='値') print('heat shape:', heat.shape); print(heat.round(-3)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:long のままだと sns.lineplot(hue='Prefecture') で都道府県ごとに色分けされた折れ線が 1 行で描ける。 wide にすると imshow(heat) で 5×12 のヒートマップが描ける(東京都が右肩上がり、 秋田県が右肩下がり)。 「同じデータでも、 図の種類で適切な形が違う」を体感できる。
「データ受領 → 形を確認 → 必要なら変換 → 集計・可視化・モデル化」が実務の典型フロー。 SSDSE-B-2026 で具体的なステップを確認します。
112 列を全部 melt するとメモリと可読性が爆発。 まず必要列だけに df[['年度','Code','Prefecture','A1101','A1303']] のように絞り、 別名にリネームしてから変換するのが鉄則。
df.isna().sum() で欠損確認df.duplicated().sum() で重複確認df.dtypes で型確認(年度が int か文字列か)df.describe() で値域に異常がないか確認long なら groupby や seaborn、 wide なら列演算や imshow。 用途に応じて使い分けます。 変換は双方向に何度でもできるので、 「迷ったらまず形を変えてみる」と発想を切り替えられる。
| シナリオ | 受領形式 | 操作目的 | 推奨 |
|---|---|---|---|
| エクセルから受領(横持ち) | wide | 統計ソフトに渡す | melt で long 化 |
| RDB から受領(縦持ち) | long | エクセル集計 | pivot_table で wide 化 |
| API JSON(ネスト構造) | 複雑 | 解析・可視化 | json_normalize → long |
| 時系列データ(年が列) | wide | トレンド比較 | そのまま使うか long 化 |
| ML 特徴量 | long | 学習データ作成 | pivot で wide 化 |
機械学習モデルの入力には「行 = 1 サンプル、 列 = 特徴量」の wide が必須。 long のままでは sklearn に渡せない。 ここでは SSDSE-B-2026 の都道府県別データから、 「過去 12 年の人口推移を特徴量とした 2023 年予測モデル」を作るための変換を実演。
このコードでやること:long → wide で「行 = 都道府県、 列 = 各年の総人口」の特徴量行列を作り、 2023 年の総人口を回帰で予測する練習。
📥 入力:df_long(1692×5、 指標==総人口に絞ると 564 行)。
1 2 3 4 5 6 7 8 9 10 11 | from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score, mean_absolute_error pop = df_long[df_long['指標'] == '総人口'].pivot_table( index='Prefecture', columns='年度', values='値') X = pop[[2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022]] y = pop[2023] m = LinearRegression().fit(X, y) yp = m.predict(X) print(f'R^2 = {r2_score(y, yp):.4f}, MAE = {mean_absolute_error(y, yp):,.0f} 人') print('X shape:', X.shape, ' y shape:', y.shape) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:long を pivot で wide 化することで「47 県 × 11 年の特徴量」と「47 県の 2023 年人口」という ML に直接使える形になった。 R²=1.0 / MAE=889 人 は 強相関の時系列を扱うと過学習傾向(前年の値が翌年をほぼ決める)。 実務では train/test 分割や時系列 CV、 特徴量の差分化が必要。 大事なのは「形を変えるだけで ML に渡せる」という事実。
pivot_table(values=['総人口', '高齢者人口'], ...) のように複数指定すると、 結果の列は MultiIndex(指標 × 年度)になる。 平坦化するには df.columns = [f'{a}_{b}' for a,b in df.columns]。
df.pivot ではなく df.pivot_table(aggfunc='mean') を使う。 重複の理由を理解した上で、 mean / sum / count などを業務に合わせて選択。
疎データを wide にすると NaN だらけになり、 メモリも食う。 そのまま long で保持し、 scipy.sparse 経由で疎行列にするのが現実解。
df.stack(level=...) / df.unstack(level=...) で指定階層だけを展開・畳み込む。 県 × 年 × 性別の 3 階層なら、 「性別だけ列に出す」のような部分変換が便利。
melt すると「値」列は全部 object 型になる(数値と文字列が混在するため)。 数値だけ抽出してから melt するか、 melt 後に pd.to_numeric(errors='coerce') で変換。
「2023年」のような文字列を pivot の columns に使うと、 並びがアルファベット順になり時系列が崩れる。 df['年度'] = df['年度'].str.replace('年','').astype(int) で数値化してから pivot。
県データと市データを 1 つのテーブルに混ぜないこと(tidy 原則 3)。 別テーブルにして、 必要に応じて concat や merge で結合。
RDB の正規形は基本 long に近い。 SELECT prefecture, year, metric, value FROM ssdse_long で取り出し、 Python 側で pivot するか、 SQL の PIVOT 構文(Oracle/SQL Server)を使う。
long / wide 変換は単なる「形の変更」ではなく、 問題を解きやすくする思考の切り替え です。 本ページで身に付けた感覚を 1 ページにまとめます。
「データを受け取ったら、 まず形を疑う」が本ページで一番伝えたいこと。 wide で受け取って long で集計、 long で受け取って wide で ML、 という 双方向の変換 ができる状態を目指してください。 SSDSE-B-2026 のような実データで何度も練習すれば、 自然に身に付きます。
本ページの 6 つのコードブロックは、 順番に Jupyter のセルに貼り付ければそのまま動きます(SSDSE-B-2026.csv を data/raw/ 配下に置くこと)。 「読むだけ → 動かして読む → 改造して読む」の 3 段階で、 long / wide 変換が「呼吸のように」できるようになります。
次のステップは、 pandas の groupby / merge / resample を体系的に押さえ、 tidy data 原則を時系列・パネルデータ・テキストデータに適用していくこと。 本ページはその起点です。
よく使うイディオムを 1 ページに集約。 印刷して机に貼っておくと作業効率が上がります。
| コード | 用途 |
|---|---|
df.melt(id_vars=['id']) | 最汎用、 id 以外を縦化 |
df.melt(id_vars=['id'], value_vars=['a','b']) | 特定列だけ縦化 |
df.set_index('id').stack() | MultiIndex 経由で縦化 |
pd.wide_to_long(df, stubnames=['pop'], i='id', j='year', sep='_') | prefix_suffix 列名規則 |
| コード | 用途 |
|---|---|
df.pivot(index='id', columns='var', values='val') | 基本、 重複不可 |
df.pivot_table(index='id', columns='var', values='val', aggfunc='mean') | 重複を集約 |
df.pivot_table(index='id', columns='var', values=['val1','val2']) | 複数指標を同時 pivot |
df.set_index(['id','var']).unstack('var') | MultiIndex 経由で wide 化 |
| コード | 用途 |
|---|---|
df.columns = [f'{a}_{b}' for a,b in df.columns] | MultiIndex 列名の平坦化 |
df.reset_index() | index を通常列に戻す |
df.rename(columns={'old':'new'}) | 列名のリネーム |
df.dropna() / df.fillna(0) | NaN 処理 |
df.drop_duplicates() | 重複行を削除 |
| コード | 用途 |
|---|---|
pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) | 日本語列名を保持して読み込み |
df[df['SSDSE-B-2026'] == 2023] | 特定年抽出 |
df.pivot_table(index=['Code','Prefecture'], columns='SSDSE-B-2026', values='A1101') | 県×年の人口クロス表 |
df.melt(id_vars=['SSDSE-B-2026','Code','Prefecture']) | 全 109 指標を long 化 |
groupby + unstack の組み合わせpolars や dask の melt / pivot が pandas より高速| 環境 | wide → long | long → wide |
|---|---|---|
| pandas (Python) | df.melt() | df.pivot_table() |
| tidyr (R) | pivot_longer() | pivot_wider() |
| polars (Python) | df.melt() | df.pivot() |
| SQL (Oracle/SQL Server) | UNPIVOT | PIVOT |
| Spark | melt() (3.4+) | groupBy().pivot() |
| Excel | パワークエリ「ピボット解除」 | ピボットテーブル機能 |
どの環境でも概念は同じ。 pandas で身に付けた感覚は他言語にそのまま転用できる。 R を主に使う人は pivot_longer / pivot_wider、 SQL を主に使う人は UNPIVOT / PIVOT を覚えておけば十分。
groupby で計算せよunstack で「指標を列に出す」変換を試せ解答例は本ページの Python 実装①〜⑥に大半が含まれています。 すべてコピペで動くので、 まず動かしてから自分で書き直す ── これが習得の近道です。
long と wide の往復は pandas の melt / pivot / wide_to_long の 3 つで完結する。 題材は SSDSE-B-2026 の年次パネル(都道府県 × 年 × 指標)で、 wide は「年が列」、 long は「年が行」に対応する。 実際に動かせるコード例は上の「🐍 Python 実装①〜⑥」がそのまま雛形になる(すべて SSDSE-B-2026 の実データ・実測値で動作を確認済み)。 ここでは「どこに変換するか」「列名の意味は何か」「欠損はどう扱うか」という判断軸を、 対応表と原則として整理しておく。
| 変換方向 | pandas | tidyverse | reshape2 (旧) |
|---|---|---|---|
| wide → long | df.melt(id_vars=...) | pivot_longer() | melt() |
| long → wide | df.pivot(index=, columns=, values=) | pivot_wider() | dcast() |
| 列名にパターン(年など) | pd.wide_to_long(df, stubnames=, i=, j=) | pivot_longer(names_pattern=) | ― |
| 集計付き wide 化 | pivot_table(aggfunc=) | pivot_wider(values_fn=) | dcast(..., fun.aggregate=) |
SSDSE-B-2026 で「年度・Code・Prefecture」を id_vars に、 A1101(総人口)などの指標列を縦に積むのが melt、 それを「行=Prefecture・列=年度」に戻すのが pivot、 「総人口_2012」のような列名規則を一括で縦持ちにするのが wide_to_long ── という対応になる(実演は Python 実装①〜④)。
| やりたいこと | 向いている形式 | 理由 |
|---|---|---|
| 2 変量の散布図を描く | 横持ち (wide) | 列をそのまま x/y に渡せる |
| 同じ変数のヒストグラム | 縦持ち (long) | フィルタ後に hist() 一発 |
| グループ別箱ひげ図 | 縦持ち (long) | seaborn の x/y/hue 引数に合う |
| 相関係数の行列計算 | 横持ち (wide) | df.corr() がそのまま使える |
| 時系列プロット (複数系列) | 縦持ち (long) | sns.lineplot(x='年', y='値', hue='指標') |
| 機械学習モデルの入力 (sklearn) | 横持ち (wide) | 特徴量列が並ぶ X 行列形式が前提 |
| GroupBy 集計 (mean/sum) | 縦持ち (long) | 「指標 × カテゴリ」の集計が直感的 |
| 人間が表として読む | 横持ち (wide) | 「1 行 = 1 主体」で目で追いやすい |
| DB に保存 (正規化) | 縦持ち (long) | 新しい指標追加で列を増やさず済む |
value) は object 型になりやすい。 数値計算前に pd.to_numeric() を通す癖をつける。pivot_table(fill_value=0) または事後の fillna() で対処。 ただし「観測なし」と「実値 0」を区別すること。(index, columns) の組が重複すると pivot は ValueError。 集約が必要なら pivot_table(aggfunc='mean') に切り替える。 SSDSE-B-2026 は「年×県」で一意なので通常は不要。astype(int) で数値化してから pivot する。Hadley Wickham が 2014 年に提唱した tidy data(整然データ)の 3 原則は、 縦持ち形式と深く関係する。
SSDSE-B-2026 の元の形式は「1 行 = 年度 × 都道府県、 列に各指標」という横持ちで、 「観測単位 = (年度, 都道府県)」と捉えれば tidy として成立する。 しかし「観測単位 = (年度, 都道府県, 指標)」と捉え直すと、 縦持ちのほうが tidy になる。 つまり tidy か否かは「何を観測単位とみなすか」に依存し、 縦持ちが絶対的に tidy なわけではない。
一般に「列数が少なく行数が多い縦持ち」は、 文字列の id 列(都道府県名・指標名など)が繰り返されるため、 同じ情報でも横持ちよりサイズが大きくなりやすい。 反対に横持ちは「疎な観測」では NaN が並んでメモリを浪費する。
| 形式 | セル数 | id 列の繰り返し | 疎データの扱い | 向くサイズ規模 |
|---|---|---|---|---|
| 横持ち (wide) | 行 × 変数列 | 少ない (1 回ずつ) | NaN が増える | 密 (dense)、 変数 << 観測 |
| 縦持ち (long) | 行 × 3 列 (id, 変数, 値) | 多い (各観測ごと) | 観測のある行だけで済む | 疎 (sparse)、 変数 >> 観測 |
| 疎行列 (sparse matrix) | 非ゼロ要素のみ | なし | 最も効率的 | 超疎 (TF-IDF, 推薦) |
ユーザー × 商品の購買履歴のように 99% が 0 になる超疎データは、 scipy.sparse の CSR/CSC 形式(実質的に「(行, 列, 値)」のトリプレット=縦持ちに近い)で保持するのが定石だ。
縦持ち ⇄ 横持ちの概念は言語を越えて共通で、 呼び名だけが異なる。 pandas で身に付けた感覚はそのまま転用できる。
| 操作 | pandas | R (tidyr) | SQL | Excel |
|---|---|---|---|---|
| 横持ち → 縦持ち | melt() / stack() | pivot_longer() | UNPIVOT | パワークエリ「ピボット解除」 |
| 縦持ち → 横持ち | pivot() / pivot_table() | pivot_wider() | PIVOT / GROUP BY + CASE | ピボットテーブル機能 |
| 複数指標の縦持ち化 | wide_to_long() | pivot_longer(names_sep=...) | 複数列の UNION | ― |
Excel ピボットの (行, 列, 値, 集計方法) は、 pandas の pivot_table の (index, columns, values, aggfunc) と 1 対 1 に対応する。 SQL で縦持ち保存しておけば、 集計の都度 GROUP BY + CASE WHEN で必要な横持ちに整形できる。
実務のデータパイプラインでは、 工程ごとに縦持ち・横持ちの使い分けが定石化している。
| 工程 | 推奨形式 | 理由 |
|---|---|---|
| データ収集 (API・センサー) | 縦持ち | 新指標追加が容易、 スキーマ変更不要 |
| データ保管 (DB・データレイク) | 縦持ち or 正規形 | 疎なデータでもメモリ効率が良い |
| 前処理・クレンジング | 縦持ち | 欠損値・型変換が groupby で楽 |
| 探索的データ解析 (EDA) | 両方使い分け | 可視化は縦、 相関は横 |
| 特徴量エンジニアリング | 横持ち | 列演算 (差分・比率) が直感的 |
| モデル学習 (sklearn) | 横持ち | X 行列 = サンプル × 特徴量が必須 |
| 結果の可視化 (seaborn / plotly) | 縦持ち | x/y/hue が縦持ち前提 |
| ダッシュボード・レポート | 横持ち | 人間が読むには横持ちが直感的 |
縦持ち・横持ちの概念は、 リレーショナルデータベース理論(1970 年代の E.F. Codd による正規化理論)まで遡る。 正規化された DB テーブル(第 3 正規形以降)は基本的に縦持ち(または狭い横持ち)の形を取る。 一方、 統計学やビジネス分析の現場では「観測ごとに 1 行、 変数ごとに 1 列」という横持ちが古くから定着し、 SPSS・SAS・Excel はすべて横持ち入力を前提に設計されている。
流れが変わったのは 2014 年、 Hadley Wickham による「Tidy Data」論文(Journal of Statistical Software)の発表だ。 これにより「縦持ち = 整然データ = 解析向き」という思想が広まり、 R の tidyr や Python の pandas(特に melt)に強い影響を与えた。 現在は「収集時は縦持ちで保存、 解析時に必要に応じて横持ちに変換」が標準パターンになっている。
日本語の「縦持ち・横持ち」は、 業務システム開発で「データを縦方向に持つか横方向に持つか」というレイアウト判断が頻出したことに由来する。 英語圏では「long format / wide format」「stacked / unstacked」「molten / cast」など複数の呼び名があるが、 概念は同じである。 ライブラリごとに用語が違う点にだけ注意したい。
下の 2 つの表はまったく同じ情報です(SSDSE-B-2026 実測値:総人口 A1101、4 都道府県 × 2021〜2023 年)。 セルをタップ(クリック)すると、 long の 1 行 ⇄ wide の 1 セルという対応関係が相互にハイライトされます。 「melt」「pivot」ボタンで変換の様子をアニメーションで再生できます。
| 県 | 年 | 総人口 |
|---|
| 県 | 2021 | 2022 | 2023 |
|---|
(県, 年) の 2 列がキー。 wide は 県 1 列がキーで、 「年」という情報は列名の中に埋まっている。
下のグラフは、 上の long 配列をそのまま「県」列でグループ化して描いたもの(系列 = グループ列の値)。 seaborn なら sns.lineplot(data=long, x='年', y='総人口', hue='県') の 1 行に対応します。 wide 形式だと列ごとにループを書く必要があり、 県が増えるたびにコードが変わってしまいます。 long 表の行やグラフの点をタップすると相互にハイライトされます。
上で体感した通り、 melt / pivot は情報を増やしも減らしもしません。 値セルはどちらも 12 個で、 変わるのは「観測を行方向に積むか、 列方向に広げるか」というレイアウトだけ。 long は「値 1 つにつき住所ラベル(県・年)を毎行書く」ので冗長だが機械に優しく、 wide は「ラベルを行見出し・列見出しに 1 回だけ書く」ので人間の目に優しい ── これが両形式のトレードオフの核心です。
NaN のセルが生まれます。 wide は「全組合せのマス目」を強制するため、 疎なデータでは欠損だらけの表になります。groupby や結合キーとして使えず、 集計のたびに melt で「救出」する必要があります。 列名が「2021_総人口」のように複数情報を含むと救出はさらに面倒に。pivot はエラー。 pivot_table(aggfunc=...) で「どう潰すか」を明示する必要があります。この操作は pandas では pd.melt(wide→long)と pivot / pivot_table(long→wide)、 R tidyverse では pivot_longer / pivot_wider に対応します。 「1 変数 = 1 列、 1 観測 = 1 行」を満たす long 形式は 整然データ(tidy data)の考え方そのもので、 データフレーム上の集計・可視化・クレンジングのすべてが滑らかになります。 実務の定石は「保存・処理は long、 提出・閲覧の直前だけ wide」です。
縦持ち・横持ち変換で頻発する失敗は、 (1) 型混在で value 列が object 化、 (2) 同一 (key, var) の重複で pivot 失敗、 (3) ID 列を value に紛れ込ませて時系列が乱れる、 の 3 つに集約されます。 Wickham の Tidy Data 原則 (各変数=1 列、 各観測=1 行) を出発点にして、 melt → 集約 → pivot の順を守れば回避できます。
df.pivot はエラーで止まる。 pivot_table(aggfunc='mean') なら集約してくれる。 SSDSE は年×県で一意なので OK だが、 顧客 ID × 日付など重複が出る場面では必須。dropna() で消すか、 そもそも疎データには long のまま扱う方が良い。df.columns = df.columns.astype(int) で戻す。.T(転置)で入れ替える。sort_values(['識別子', '年度']) で並べ直してから時系列処理へ。関連概念を視覚的に整理した概念マップ。
縦持ち (long) と横持ち (wide) は同じ情報の異なる表現で、 中央ノード「観測単位 × 変数」を起点に、 long 形式は「(都道府県, 年, 指標, 値)」の 4 列、 wide 形式は「都道府県を行・年を列」の交差表として展開される。 pandas の melt / pivot が両者を結ぶ可逆操作で、 集計・可視化・回帰モデル投入時には用途に応じて使い分ける。
long/wide 変換は EDA → 可視化 → モデリングの結節点であり、 隣接領域と密接につながる:
long ⇄ wide 変換は EDA → 可視化 → モデリングのパイプラインの結節点。
「long と wide のどちらで持つか」は次工程で決まる。 3 段で判定する。
relplot / catplot / lineplot → long 形式 (hue/col に列名を指定する設計)pd.melt(id_vars='都道府県', var_name='年', value_name='人口') で long 化pivot_table(aggfunc='mean') で集約か、 重複を解消fillna(0) かinterpolate() を意図的に選択原則: 「分析は long、 表示は wide」。 SSDSE-B-2026 (年×都道府県×指標) は元から long に近い構造なので、 melt 不要なケースが多い。
本ページ冒頭の「🎨 直感で掴む」を、 言葉の解像度を 1 段上げて言い直しておく。 long / wide の混乱の多くは「行が何を表すか」の取り違えに由来するので、 ここを固めると以降の落とし穴が一気に見通せる。
(個体, 変数, 値) という3 つ組(triple)を表す。 「北海道・総人口・5092000」で 1 行。 個体は複数行に分散し、 変数名すらデータ(値)として列に降りてくる。(個体, 変数) の粒度」まで細かく割った形で、 このとき「1 変数 1 列・1 観測 1 行」を満たす。 ただしlong なら自動的に tidy data というわけではない。 「観測単位を何に取るか」で tidy か否かが決まる点は、 本ページ「D. tidy data と『観測単位』の関係」で詳述した通り。 SSDSE-B-2026 は「観測単位=(年, 県)」と見れば元の wide のままで tidy、 「観測単位=(年, 県, 指標)」と見れば long が tidy になる。
本ページには既に「⚠️ よくある落とし穴」「long / wide 変換でハマる 6 連発」「C. melt / pivot の落とし穴」があるが、 ここでは特に事故が多い 5 点を、 SSDSE-B-2026(564 行 × 112 列、 うち指標 109 列)の実測値で裏取りしながら掘り下げる。
df.melt() を引数なしで呼ぶと、 全 112 列が無差別に縦積みされる。 SSDSE-B-2026 での実測は次の通り(本ページの Python 実装が動く環境でそのまま再現できる)。
指定漏れ版では Prefecture が「変数」として 564 回 value 列に混入し、 文字列と数値が同居するため value 列が object 型に落ちる(=mean() 等の数値計算が即エラー)。 正しく識別子 3 列を id_vars に渡すと value は float64 のまま。 「melt したら真っ先に df['value'].dtype を見る」を癖にする。 型の話は 型変換 も参照。
同じ欠測でも long と wide で挙動が真逆になる。 これは「⚠️ よくある落とし穴(変換で起きること)」で触れた点の両方向の整理である。
NaN として顕在化する。 疎なデータほど NaN だらけの表になる。dropna() すると、 「たまたまその年に観測が無かっただけの個体」が丸ごと落ちる。pivot_table(fill_value=0) は便利だが、 「測っていない」を「0 だった」に書き換えてしまう危険がある。 欠測の意味(欠測メカニズム)を確認してから埋める。 詳しくは 欠損値 を参照。wide の列名「2021」「2022」「2023」は、 実は「年」という変数の値である。 列名に埋まった情報は groupby のキーにも結合キーにもできず、 集計のたびに melt で「救出」する羽目になる。 列名が「総人口_2021」のように複数の情報を連結していると救出はさらに厄介で、 pd.wide_to_long や str.split での分解が要る(本ページ Python 実装④が実演)。
「人口(人)」と「高齢化率(%)」のように単位の違う指標を 1 本の value 列に積むと、 groupby('指標').mean() は指標ごとには正しいが、 指標をまたいだ value.mean() は物理的に無意味な数になる。 long 化するときは「値が同質か」を必ず問う。 本ページ「手法選択フロー Step 2」の判定軸がこれに当たる。
最後は形式の選択ミスそのもの。 seaborn の lineplot(hue=...) や箱ひげは long、 df.corr() や sklearn の X 行列は wide が前提。 「図が描けない・モデルに渡せない」の大半は、 手法の要求形式を確認せずに変換した取り違えが原因。 可視化 の入力仕様を先に見てから形を決めるとムダな往復が減る。
long / wide を「単なる整形」で終わらせず、 統計モデル・データベース理論と接続すると、 なぜ long が分析の基本形とされるのかが腑に落ちる。
SSDSE-B-2026 は「都道府県 i × 年 t」のパネルデータそのもの。 パネル分析のツールは long を要求する ── statsmodels の PanelOLS は MultiIndex(entity, time)、 すなわち「(個体, 時点) を行に積んだ long」を入力形式とする。 固定効果モデルや パネル因果推論(差分の差分など)は、 個体 i と時点 t がそれぞれ 1 列として存在する long でこそ「個体ダミー」「時点ダミー」を素直に組める。 wide のままでは t が列名に埋もれてしまい、 これらのモデルに渡せない(落とし穴③の帰結)。
本ページの対応表でも触れた通り、 stack/unstack は「インデックス階層と列階層を入れ替える」操作で、 melt / pivot の MultiIndex 特化版と捉えると迷わない。
df.set_index(['Code','Prefecture']).stack() ── 列を最内インデックスへ畳んで long 化(縦長 Series)。df.unstack('年度') ── インデックスの「年度」階層だけを列へ展開。 「性別だけ横に出す」のような部分変換が melt / pivot より簡潔。MultiIndex を持つなら stack / unstack。long 形式はデータベースの正規化と本質的に近い。 wide の「年ごとに繰り返す列(2012, 2013, …)」は、 リレーショナル理論でいう反復グループであり、 これを行に畳んで (個体, 年, 値) にする melt は、 第 1 正規形へ向かう整形とほぼ同義。 リレーショナルデータベースの正規形テーブルが自然と long(ファクトテーブル)に寄るのはこのため。
long のカテゴリ列を pivot で列展開すると、 各カテゴリが 0/1(または値)の指示列になる ── これは one-hot エンコーディングと地続きの発想である。 「指標を列に出す」pivot、 「カテゴリを列に出す」one-hot、 いずれも値を列見出しへ昇格させる同じ操作の顔違い。 疎になりやすい点も共通で、 超疎なら scipy.sparse(本ページ E 節参照)に逃がすのが定石。