5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

総務省統計局の家計調査データを Livebook で取得、加工、分析する

Last updated at Posted at 2023-02-20

はじめに

総務省統計局は日本の各種統計データを作成しています

統計局の Web サイトでは様々なデータを公開しており、利用規約に従ってこれらのデータを利用することができます

というわけで、今回は Livebook から統計局の家計調査データを取得し、加工、分析してみたいと思います

実装したノートブックはこちら

出典

総務省統計局ホームページ

家計調査(家計収支編) 時系列データ(二人以上の世帯)

https://www.stat.go.jp/data/kakei/longtime/index.html#time

  • 月 全品目(2015年改定)
  • 月 全品目(2020年改定)

(2023年2月20日に利用)

セットアップ

必要なモジュールをインストールします

Mix.install(
  [
    {:explorer, "~> 0.5"},
    {:codepagex, "~> 0.1"},
    {:kino, "~> 0.8"},
    {:kino_vega_lite, "~> 0.1"},
    {:req, "~> 0.3"}
  ],
  config: [
    codepagex: [
      encodings: ["VENDORS/MICSFT/WINDOWS/CP932"]
    ]
  ]
)

今回使うデータは文字コードが Shift_JIS になっているため、 Codepagex を使って UTF-8 に変換します

config で Codepagex で Shift_JIS を使うための設定をしています

準備

Explorer を使うための準備をします

alias Explorer.DataFrame
alias Explorer.Series
require Explorer.DataFrame

alias によって Explorer. を省略できるようになります

require Explorer.DataFrame によって、データ解析時にクエリが使えるようになります

詳細は以下の記事を参照してください

Shift_JIS を短く指定できるようにしておきます

shift_jis = "VENDORS/MICSFT/WINDOWS/CP932"

データ取得

統計局の家計収支データ(二人以上の世帯)をダウンロードし、文字列として読み込みます

まずは2020年改訂の品目別月次をダウンロードしてみます

このデータは世帯平均でいつ、どういう品目にいくら使ったか、を見ることができます

例えば2022年4月、各家庭でお米をどれくらい買ったかの平均が分かります

string_value =
  "https://www.stat.go.jp/data/kakei/longtime/csv/h-mon-2020.csv"
  |> Req.get!()
  |> then(&Codepagex.to_string!(&1.body, shift_jis))

Codepagex.to_string で文字コードを UTF-8 に変換しています

これをしないとバイナリのままです

結果は以下のようになります

"二人以上の世帯_支出金額[円],,,,,,,,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,..."

これを見ても何の事だと思いますが、CSVデータが文字列として取得できています

整形

データフレームへの読込

文字列を改行 \n で分割し、先頭2行は不要なのでスキップします

各行をカンマ , で分割することで各列の値にします

結果を [header_row | data_rows] で受けることにより、ヘッダー行(列名の行)と、データ行に分割します

[header_row | data_rows] =
  string_value
  |> String.split("\r\n")
  |> Enum.slice(3..-1)
  |> Enum.map(&String.split(&1, ","))

結果は以下のようになります

[
  ["表側連番", "階層", "大分類", "中分類", "小分類", "中間計", "符号",
   "品目分類", "1月", " 2月", " 3月", " 4月", " 5月", " 6月", " 7月", " 8月", " 9月",
   " 10月", " 11月", " 12月", " 1月", " 2月", " 3月", " 4月", " 5月", " 6月", " 7月",
   " 8月", " 9月", " 10月", " 11月", " 12月", " 1月", " 2月", " 3月", " 4月", " 5月",
   " 6月", " 7月", " 8月", " 9月", " 10月", " 11月", " 12月"],
  ["1", "-", "-", "-", "-", "-", "-", "世帯数分布(抽出率調整)", "10000", "10000", "10000",
   "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000",
   "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000",
   "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000", "10000",
   "10000", "10000", "10000"],
  ...
]

最初の7列分は良いのですが、それより後ろは各年(2020年から2022年)の月が繰り返されています

列名が重複しているのは悪いので、ヘッダー行を修正します

まず先頭7列は別に分けておきます

fixed_cols = Enum.slice(header_row, 0..7)

各月の列を YYYY-MM-DD に変換します

month_cols =
  header_row
  # 8列目以降
  |> Enum.slice(8..-1)
  # M月をMMに変換
  |> Enum.map(fn month ->
    month
    |> String.trim()
    |> String.replace("月", "")
    |> then(&String.slice("0" <> &1, -2..-1))
  end)
  # 年毎に分ける
  |> Enum.chunk_every(12)
  # 対象年
  |> Enum.zip([2020, 2021, 2022])
  " MMをYYYY-MM-DDに変換する
  |> Enum.flat_map(fn {month_list, year} ->
    Enum.map(month_list, fn month -> "#{year}-#{month}-01" end)
  end)
  |> dbg()

dbg で各パイプの中身を見るとこんな感じです

dbg.gif

加工した列を元の場所に戻します

header_row = fixed_cols ++ month_cols

結果は以下のようになります

["表側連番", "階層", "大分類", "中分類", "小分類", "中間計", "符号",
 "品目分類", "2020-01-01", "2020-02-01", "2020-03-01", "2020-04-01", "2020-05-01", "2020-06-01",
 "2020-07-01", "2020-08-01", "2020-09-01", "2020-10-01", "2020-11-01", "2020-12-01", "2021-01-01",
 "2021-02-01", "2021-03-01", "2021-04-01", "2021-05-01", "2021-06-01", "2021-07-01", "2021-08-01",
 "2021-09-01", "2021-10-01", "2021-11-01", "2021-12-01", "2022-01-01", "2022-02-01", "2022-03-01",
 "2022-04-01", "2022-05-01", "2022-06-01", "2022-07-01", "2022-08-01", "2022-09-01", "2022-10-01",
 "2022-11-01", "2022-12-01"]

加工したヘッダー行とデータ行を改めてくっつけて、 DataFrame.load_csv! で読み込みます

df_2020 =
  [header_row | data_rows]
  |> Enum.map(&Enum.join(&1, ","))
  |> Enum.join("\n")
  |> DataFrame.load_csv!()

Kino.DataTable.new(df_2020)

データは以下のようになり、品目分類毎に各年月で何円消費したかを表します

table.png

データの結合

2015年改訂分のデータも同様に読み込みます

こちらは2015年から2019年までのデータです

df_2015 =
  "https://www.stat.go.jp/data/kakei/longtime/csv/h-mon-2015.csv"
  |> Req.get!()
  |> then(&Codepagex.to_string!(&1.body, shift_jis))
  |> String.split("\r\n")
  |> Enum.slice(3..-1)
  |> Enum.map(&String.split(&1, ","))
  |> then(fn [header_row | data_rows] ->
    fixed_cols = Enum.slice(header_row, 0..7)

    month_cols =
      header_row
      |> Enum.slice(8..-1)
      |> Enum.map(fn month ->
        month
        |> String.trim()
        |> String.replace("月", "")
        |> then(&String.slice("0" <> &1, -2..-1))
      end)
      |> Enum.chunk_every(12)
      |> Enum.zip(2015..2019)
      |> Enum.flat_map(fn {month_list, year} ->
        Enum.map(month_list, fn month -> "#{year}-#{month}-01" end)
      end)

    header_row = fixed_cols ++ month_cols

    [header_row | data_rows]
    |> Enum.map(&Enum.join(&1, ","))
    |> Enum.join("\n")
  end)
  |> DataFrame.load_csv!()

Kino.DataTable.new(df_2015)

2020年改訂分と2015年改訂分を結合して8年分のデータにします

DataFrame.join でデータフレーム同士で同じ名前の列をキーとして結合します

2020年改訂分と2015年改訂分では品目が変わっているため、 how: :outer を指定し、外部結合します

また、「表側連番」の項目は名前の通り連番なので、2020年改訂分と2015年改訂分では同じ品目コードでも違う値になっています

「表側連番」を結合のキーにしてはいけないため、それ以外の項目で結合します

df_all =
  DataFrame.join(
    DataFrame.select(df_2015, &(&1 != "表側連番")),
    DataFrame.select(df_2020, &(&1 != "表側連番")),
    how: :outer
  )

Kino.DataTable.new(df_all)

これによって8年分のデータが作成できました

ピボット

 各年月が列になっているのは時系列で分析したいときに不便です

年月を一つの列に変換してしまいましょう

DataFrame.pivot_longer で横方向に伸びていた(wideだった)データを縦方向に伸ばします(longにする)

pivot_df =
  DataFrame.pivot_longer(
    df_all,
    &String.starts_with?(&1, "20"),
    names_to: "年月",
    values_to: "支出金額"
  )

Kino.DataTable.new(pivot_df)

ピボットされた結果は以下のようになります

pivot.png

これで、ある品目分類にいつどれくらい支出したのか分かりやすくなりました

年列、月列の追加

年月の列では年や月で集計したいときに不便なので、年だけ、月だけの列も作っておきます

date_list = Series.to_list(pivot_df["年月"])

month_series =
  date_list
  |> Enum.map(&(&1 |> String.split("-") |> Enum.at(1) |> String.to_integer()))
  |> Series.from_list()

year_series =
  date_list
  |> Enum.map(&(&1 |> String.split("-") |> Enum.at(0) |> String.to_integer()))
  |> Series.from_list()

household_df =
  pivot_df
  |> DataFrame.put("月", month_series)
  |> DataFrame.put("年", year_series)

Kino.DataTable.new(household_df)

結果は以下のようになります

year_month.png

加工したデータのダウンロード

Kino.Download.new でデータをローカルにダウンロードできます

Kino.Download.new(
  fn -> DataFrame.dump_csv!(household_df) end,
  filename: "家計支出統計_品目年月別.csv"
)

実行すると、以下のようなダウンロードボタンが表示され、そこからファイルとしてダウンロードできます

download_btn.png

加工結果はリポジトリーにも保存しています(次回使います)

マスター情報

大分類などのマスター情報を確認します

集約項目一覧

大分類にも含まれない、品目分類ではない項目です

household_df
|> DataFrame.filter(大分類 == "-")
|> DataFrame.distinct(["品目分類"])
|> Kino.DataTable.new()

結果の一部は以下のようなものです

  • 世帯数分布(抽出率調整)
  • 集計世帯数
  • 世帯人員(人)
  • 18歳未満人員(人)
  • 65歳以上人員(人)
  • うち無職者人員(人)
  • 有業人員(人)
  • 世帯主の年齢(歳)
  • 持家率(%)

品目別の支出額と関連付けて考察することができそうです

品目分類

大分類は以下のようにして取得できます

household_df
|> DataFrame.filter(
  大分類 != "-" and
    中分類 == "-" and
    小分類 == "-" and
    中間計 == "-" and
    符号 == "-"
)
|> DataFrame.distinct(["大分類", "品目分類"])
|> Kino.DataTable.new()

大分類は以下の10分類です

大分類 名称
1 食料
2 住居
3 光熱・水道
4 家具・家事用品
5 被服及び履物
6 保健医療
7 交通・通信
8 教育
9 教養娯楽
10 その他の消費支出

中分類、小分類も同様です

household_df
|> DataFrame.filter(
  大分類 != "-" and
    中分類 != "-" and
    小分類 == "-" and
    中間計 == "-" and
    符号 == "-"
)
|> DataFrame.distinct(["大分類", "中分類", "品目分類"])
|> Kino.DataTable.new()

中分類一部抜粋

大分類 中分類 名称
1 1 穀類
1 2 魚介類
1 3 肉類
1 4 乳卵類
1 5 野菜・海藻
1 6 果物
1 7 油脂・調味料
1 8 菓子類
1 9 調理食品
1 10 飲料
1 11 酒類
1 12 外食
household_df
|> DataFrame.filter(
  大分類 != "-" and
    中分類 != "-" and
    小分類 != "-" and
    中間計 == "-" and
    符号 == "-"
)
|> DataFrame.distinct(["大分類", "中分類", "小分類", "品目分類"])
|> Kino.DataTable.new()

小分類一部抜粋

大分類 中分類 小分類 名称
1 1 2 パン
1 1 3 麺類
1 1 4 他の穀類
1 2 1 生鮮魚介
1 2 2 塩干魚介
1 2 3 魚肉練製品
1 2 4 他の魚介加工品
1 3 1 生鮮肉
1 3 2 加工肉

小分類よりも細かい中間計もあります

household_df
|> DataFrame.filter(
  大分類 != "-" and
    中分類 != "-" and
    小分類 != "-" and
    中間計 != "-" and
    符号 == "-"
)
|> DataFrame.distinct(["中間計", "品目分類"])
|> Kino.DataTable.new()

中間計の一部抜粋

中間計 名称
170-189 鮮魚
190-194 貝類
240-249 葉茎菜
250-259,25X 根菜
260-269,26B,26X 他の野菜

最小単位は符号で、具体的な品目名になっています

household_df
|> DataFrame.filter(符号 != "-")
|> DataFrame.distinct(["符号", "品目分類"])
|> Kino.DataTable.new()

符号の一部抜粋

符号 名称
340 ようかん
341 まんじゅう
342 他の和生菓子
343 カステラ
344 ケーキ
347 ゼリー
348 プリン
345 他の洋生菓子

ケーキの支出推移

データをグラフにしてみましょう

前述した符号の一覧で、「ケーキ」の符号が「344」と言うことが分かるので、ケーキのデータだけを抽出します

cake_df = DataFrame.filter(household_df, 符号 == "344")

Kino.DataTable.new(cake_df)

結果は以下のようになります

cake_table.png

では支出金額推移をグラフ化してみましょう(勘のいい人はおおよその形が予想できると思います)

month = Series.to_list(cake_df["年月"])
expenses = Series.to_list(cake_df["支出金額"])

VegaLite.new(width: 700, title: "ケーキ支出金額推移")
|> VegaLite.data_from_values(x: month, y: expenses)
|> VegaLite.mark(:line, tooltip: true)
|> VegaLite.encode_field(:x, "x", type: :temporal, title: "年月")
|> VegaLite.encode_field(:y, "y", type: :quantitative, title: "支出金額")

cake_graph.png

心電図のように一定周期で脈打っています

2022年だけに絞ってみましょう

latest_cake_df = DataFrame.filter(cake_df,  == 2022)

month = Series.to_list(latest_cake_df["月"])
expenses = Series.to_list(latest_cake_df["支出金額"])

VegaLite.new(width: 700, title: "ケーキ支出金額推移")
|> VegaLite.data_from_values(x: month, y: expenses)
|> VegaLite.mark(:line, tooltip: true)
|> VegaLite.encode_field(:x, "x", type: :ordinal, title: "年月")
|> VegaLite.encode_field(:y, "y", type: :quantitative, title: "支出金額")

cake_graph_2022.png

12月のクリスマス特需が突き抜けていることが分かります

どれくらい突出しているのか数値化してみましょう

latest_cake_df
|> DataFrame.group_by("品目分類")
|> DataFrame.mutate(
  最小: min(支出金額),
  最大: max(支出金額),
  最小最大比: max(支出金額) / min(支出金額)
)
|> DataFrame.select(["最小", "最大", "最小最大比"])
|> DataFrame.to_rows()
|> List.first()

結果は以下のようになります

%{
  "品目分類" => "ケーキ",
  "最大" => 1411.0,
  "最小" => 482.0,
  "最小最大比" => 2.9273858921161824
}

一番低い7月と比べて、一番高い12月は約3倍ケーキにお金を掛けています

まとめ

総務省統計局のデータを使えばいろいろ調査できそうですね

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?