e-Stat の家計調査データから業界の成長・衰退や新型コロナの影響を可視化する

## はじめに

以前の記事で総務省統計局の家計調査データを Elixir Livebook と Explorer で可視化しました

https://qiita.com/RyoWakabayashi/items/4bac70408c80426180e5

https://qiita.com/RyoWakabayashi/items/ec1a45e39828b2dfff1f

実は同じようなデータを取得できる API があります

政府統計の総合窓口 e-Stat です

https://www.e-stat.go.jp/

> 各府省等が実施している統計調査の各種情報をこのサイトからワンストップで提供することを目指し、各府省等が公表する統計データ、公表予定、新着情報、調査票項目情報などの各種統計情報をインターネットを通して利用いただくことができます。

[総務省統計局](https://www.stat.go.jp/)が整備し、[独立行政法人統計センター](https://www.nstac.go.jp/)が運用管理を行っています

今回は e-Stat の API から家計調査データを取得し、さまざまな品目の消費増加、減少やコロナによる影響を可視化してみます

今回も ELixir Livebook を使っていきます

https://livebook.dev/

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

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/e_stat/e_stat_household_expense.livemd

## 出典

政府統計の総合窓口(e-Stat) <https://www.e-stat.go.jp/>

「家計調査 / 家計収支編 総世帯 品目分類（2020年改定）（金額）」（統計局統計調査部消費統計課）を加工して利用

## アプリケーションIDの取得

e-Stat の API 認証に使うアプリケーションIDを取得しておきます

[e-Stat](https://www.e-stat.go.jp/) TOPページ右上の「新規登録」からアカウントを作成し、ログインしてください

![e-stat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/5d03a289-0dc9-13e6-4d67-51afa680669b.png)

ログインすると「マイページ」のリンクが表示されるのでクリックしてください

![e-stat-login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/5f79f369-0b49-132d-b292-a1f241453b7f.png)

「API機能（アプリケーションID発行）」のリンクをクリックします

![e-stat-my-page.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c61f4695-b1dd-0416-3828-95b4728194eb.png)

今回はローカルで動かすだけなので、「名称」と「URL」に適当な値を入れて、「発行」ボタンをクリックすると、 appId のところにアプリケーションIDが表示されます

:::note info
「URL」について、説明欄に以下のように記載されています（なぜか英語だけで）

> * If you do not use it on the public site, please enter "http: // test.localhost /" etc.
:::

![e-stat-api-id.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/81eac0ff-8dab-6a24-512d-f6925e1c1706.png)

## セットアップ

Livebook を開き、ノートブックで以下のコードを実行して必要なモジュールをインストールします

```elixir
Mix.install([
  {:explorer, "~> 0.5"},
  {:kino, "~> 0.8"},
  {:kino_vega_lite, "~> 0.1"},
  {:req, "~> 0.3"}
])
```

- [Explorer](https://github.com/elixir-nx/explorer): データ分析
- [Kino](https://github.com/livebook-dev/kino): 強力な UI/UX
- [Kino VegaLite](https://github.com/livebook-dev/kino_vega_lite): グラフ描画
- [Req](https://github.com/wojtekmach/req): HTTP通信(データダウンロード)

## 準備

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

```elixir
alias Explorer.DataFrame
alias Explorer.Series
require Explorer.DataFrame
```

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

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

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

https://qiita.com/RyoWakabayashi/items/053296b36d8744aca9ca

## データ取得

`Kino.Input.password` でテキストボックスを表示し、 e-Stat のマイページからアプリケーションIDをコピー＆ペーストします

```elixir
app_id_input = Kino.Input.password("APP ID")
```

e-Stat から CSV 形式でデータを取得する場合の URL を用意します

```elixir
api_path = "http://api.e-stat.go.jp/rest/3.0/app/getSimpleStatsData"
```

ちなみに、 XML 形式場合は <http://api.e-stat.go.jp/rest/3.0/app/getStatsData> で、 JSON 形式の場合は <http://api.e-stat.go.jp/rest/3.0/app/json/getStatsData> です

API の詳細な仕様はこちら

https://www.e-stat.go.jp/api/api-info/e-stat-manual3-0

普通であれば JSON で取得するところですが、この JSON や XML の仕様が非常に分かりづらく扱いづらいため、今回はシンプルな CSV で取得します

続いて、今回対象とするデータの ID を用意します

```elixir
data_id = "0003348234"
```

このデータは「家計調査 / 家計収支編 総世帯 品目分類（2020年改定）（金額）」です

日本のすべての世帯について、品目分類毎、年毎に何円使ったか、というデータになっています

Req を使ってデータを取得します

```elixir
response_body =
  "#{api_path}?appId=#{Kino.Input.read(app_id_input)}&statsDataId=#{data_id}"
  |> Req.get!()
  |> then(& &1.body)
```

結果は以下のような文字列になっています

```elixir
"\"RESULT\"\n\"STATUS\",\"0\"\n\"ERROR_MSG\",\"正常に終了しました。\"\n\"DATE\",\"2023-02-25T20:32:54.145+09:00\"\n..."
```

まずは `\n` (改行)で文字列を分割して List にします

```elixir
rows = String.split(response_body, "\n")
```

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

```elixir
["\"RESULT\"", "\"STATUS\",\"0\"", "\"ERROR_MSG\",\"正常に終了しました。\"",
 "\"DATE\",\"2023-02-25T20:32:54.145+09:00\"", "\"RESULT_INF\"", "\"TOTAL_NUMBER\",\"21798\"",
 "\"FROM_NUMBER\",\"1\"", "\"TO_NUMBER\",\"21798\"", "\"TABLE_INF\",\"0003348234\"",
 "\"STAT_NAME\",\"00200561\",\"家計調査\"", "\"GOV_ORG\",\"00200\",\"総務省\"",
 "\"STATISTICS_NAME\",\"家計調査 家計収支編 総世帯\"",
 "\"TITLE\",\"001\",\"品目分類 品目分類（2020年改定）（金額）\"",
 "\"CYCLE\",\"年次\"", "\"SURVEY_DATE\",\"0\"", "\"OPEN_DATE\",\"2023-02-07\"",
 "\"SMALL_AREA\",\"0\"", "\"COLLECT_AREA\",\"該当なし\"",
 "\"MAIN_CATEGORY\",\"07\",\"企業・家計・経済\"", "\"SUB_CATEGORY\",\"04\",\"家計\"",
 "\"OVERALL_TOTAL_NUMBER\",\"20420\"", "\"UPDATED_DATE\",\"2023-02-07\"",
 "\"STATISTICS_NAME_SPEC\",\"家計調査\",\"家計収支編\",\"総世帯\",\"\",\"\",\"\"",
 "\"TITLE_SPEC\",\"品目分類\",\"品目分類（2020年改定）（金額）\",\"\",\"\",\"\",\"\"",
 "\"NOTE\",\"***\",\"調査又は集計していないもの\"",
 "\"NOTE\",\"-\",\"該当数字がないもの\"",
 "\"NOTE\",\"X\",\"数値が秘匿されているもの\"", "\"VALUE\"",
 "\"tab_code\",\"表章項目\",\"cat01_code\",\"品目分類（2020年改定）\",\"cat02_code\",\"世帯区分\",\"area_code\",\"地域区分\",\"time_code\",\"時間軸（年次）\",\"unit\",\"value\",\"annotation\"",
 "\"01\",\"金額\",\"000100000\",\"世帯数分布(抽出率調整)\",\"11\",\"総世帯\",\"00000\",\"全国\",\"2007000000\",\"2007年\",\"一万分比\",\"10000\",\"\"",
 ...
]
```

先頭の数行はメタデータになっているため、 CSV として読み込む場合はスキップする必要があります

`VALUE` の文字がある行の次の行からが実際の CSV データなので、 `Enum.find_index` を使って `VALUE` が何行目にあるか取得します

```elixir
value_index = Enum.find_index(rows, &(&1 == "\"VALUE\""))
```

Enum.slice で `VALUE` の次の行から最後の行までを抜き出し、再び `\n` で結合して文字列にしてからデータフレームに読み込みます

```elixir
expenses_df =
  rows
  |> Enum.slice((value_index + 1)..-1)
  |> Enum.join("\n")
  |> DataFrame.load_csv!()

Kino.DataTable.new(expenses_df)
```

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

![all_df.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/32d31dbd-50e0-7731-a8fe-52d174ce5128.png)

https://www.stat.go.jp/data/kakei/kaisetsu.html#p5

何年分のデータなのか確認してみます

```elixir
expenses_df["時間軸（年次）"]
|> Series.frequencies()
|> DataFrame.arrange(asc: values)
|> Kino.DataTable.new()
```

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

|values|counts|
|-----:|-----:|
|2007年|1338|
|2008年|1338|
|2009年|1338|
|2010年|1352|
|2011年|1352|
|2012年|1352|
|2013年|1352|
|2014年|1352|
|2015年|1378|
|2016年|1378|
|2017年|1378|
|2018年|1378|
|2019年|1378|
|2020年|1378|
|2021年|1378|
|2022年|1378|

つまり、このデータは 2007年から 2022年までの期間について品目分類毎の支出金額を保持しています

何段階かで品目分類の数が増えているのは、見直しによる品目の追加によるものです

品目分類についてはこちらに一覧があります

https://www.stat.go.jp/data/kakei/koumoku/bunrui2020.html

しかし、非常に分かりづらいのでデータフレームから品目分類の一覧を抽出してみましょう

データフレームから `cat01_code` と `品目分類（2020年改定）` の項目について、重複を排除して取得します

```elixir
expenses_df[["cat01_code", "品目分類（2020年改定）"]]
|> DataFrame.distinct()
|> Kino.DataTable.new()
```

結果は以下のようになり、全部で 689 分類になっています

`cat01_code` は9桁のコードになっており、その先頭2桁が大分類になっています

|cat01_code|品目分類（2020年改定）|
|---------:|:----------------|
|100000|世帯数分布(抽出率調整)|
|200000|集計世帯数|
|300000|世帯人員|
|400000|18歳未満人員|
|500000|65歳以上人員|
|600000|65歳以上無職者人員|
|700000|有業人員|
|800000|世帯主の年齢|
|900000|持家率|
|1000000|家賃・地代を支払っている世帯の割合|
|1100000|消費支出|
|10000000|1 食料|
...

大分類だけを抜き出してみましょう

```elixir
expenses_df[["cat01_code", "品目分類（2020年改定）"]]
|> DataFrame.filter(
  remainder(cat01_code, 10_000_000) == 0 and
  cat01_code <= 100_000_000
)
|> DataFrame.distinct()
|> Kino.DataTable.new()
```

大分類は以下の１０分類です

|cat01_code|品目分類（2020年改定）|
|---------:|:----------------|
|10000000|1 食料|
|20000000|2 住居|
|30000000|3 光熱・水道|
|40000000|4 家具・家事用品|
|50000000|5 被服及び履物|
|60000000|6 保健医療|
|70000000|7 交通・通信|
|80000000|8 教育|
|90000000|9 教養娯楽|
|100000000|10 その他の消費支出|

続いて `cat02_code` について見てみます

```elixir
expenses_df[["cat02_code", "世帯区分"]]
|> DataFrame.distinct()
|> Kino.DataTable.new()
```

`cat02_code` は世帯区分のコードになっています

|cat02_code|世帯区分 |
|---------:|:-------|
|        11|総世帯   |
|        12|勤労者世帯|

世帯区分についてはこちらに説明があります

https://www.stat.go.jp/data/kakei/kaisetsu.html#p5

>「勤労者世帯」とは、世帯主が会社、官公庁、学校、工場、商店などに勤めている世帯をいう。ただし、世帯主が社長、取締役、理事など会社団体の役員である世帯は「勤労者・無職以外の世帯」とする。
>
>「無職世帯」とは、世帯主が無職である世帯をいう。例えば、年金、恩給、仕送り金、保険取金、財産収入等により家計を営んでいる世帯をいう。
>
>「勤労者・無職以外の世帯」とは、勤労者世帯及び無職世帯以外の世帯をいう。

つまり、「総世帯」はすべての世帯を合計したもの、「勤労者世帯」は無職と経営者以外の世帯を示しています

## 大分類毎の割合

2022年時点の大分類毎の家計に占める割合を算出してみます

まず、以下の条件でデータを抽出し、2022年の総世帯合計支出を取得します

- time_code=「2022000000」（2022年）
- cat02_code=「11」(総世帯)
- cat01_code=「1100000」（消費支出）

```elixir
total =
  expenses_df
  |> DataFrame.filter(
    time_code == 2_022_000_000 and
      cat02_code == 11 and
      cat01_code == 1_100_000
  )
  |> DataFrame.pull("value")
  |> Series.to_list()
  |> List.first()
```

結果は `2930777.0` になります

つまり、すべての世帯で平均すると、年間 293 万円支出しているわけです

1ヶ月だと24万４千円ですね

各大分類の支出金額を取得し、総世帯合計支出で割ることで割合を算出します

```elixir
latest_df =
  expenses_df
  |> DataFrame.filter(
    time_code == 2_022_000_000 and
      cat02_code == 11 and
      remainder(cat01_code, 10_000_000) == 0 and
      cat01_code <= 100_000_000
  )
  |> DataFrame.select(["品目分類（2020年改定）", "value"])
  |> DataFrame.rename(["品目分類", "支出金額"])
  # 小数点以下1桁の％に変換
  |> DataFrame.mutate(割合: cast(支出金額 / ^total * 1000, :integer) / 10)

Kino.DataTable.new(latest_df)
```

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

圧倒的に食料が大きいです

|品目分類          |  支出金額| 割合|
|:---------------|--------:|---:|
| 1 食料          | 815241.0|27.8|
| 2 住居          | 244101.0| 8.3|
| 3 光熱・水道      | 244798.0| 8.3|
| 4 家具・家事用品   |119283.0| 4.0|
| 5 被服及び履物    | 95661.0| 3.2|
| 6 保健医療       |145677.0| 4.9|
| 7 交通・通信      |402269.0|13.7|
| 8 教育          |  87704.0| 2.9|
| 9 教養娯楽      | 292750.0| 9.9|
|10 その他の消費支出|483291.0|16.4|

もっと視覚的に、棒グラフにして大きい順に並べてみましょう

```elixir
items = Series.to_list(latest_df["品目分類"])
expenses = Series.to_list(latest_df["支出金額"])

VegaLite.new(width: 700, title: "支出金額内訳")
|> VegaLite.data_from_values(x: items, y: expenses)
|> VegaLite.mark(:bar, tooltip: true)
|> VegaLite.encode_field(:x, "x", type: :nominal, title: "品目分類", sort: "-y")
|> VegaLite.encode_field(:y, "y", type: :quantitative, title: "支出金額")
```

![e-stat_bar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/bb7b8ad5-1c57-049f-605f-2aad6f05e42f.png)

円グラフも使いましょう

```elixir
VegaLite.new(title: "支出金額内訳")
|> VegaLite.data_from_values(x: items, y: expenses)
|> VegaLite.mark(:arc, inner_radius: 50, tooltip: true)
|> VegaLite.encode_field(:color, "x", type: :nominal, title: "品目分類")
|> VegaLite.encode_field(:theta, "y", type: :quantitative, title: "支出金額")
```

![e-stat_arc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/2d6582fa-03e8-236f-b5cd-f65aa58d7053.png)

自分の家計と比べてみると面白いかもしれません

## 各値の推移

せっかくの年次データなので、各値の推移を線グラフで見てみましょう

品目分類を List で取得し、先グラフをプロットする関数を用意します

```elixir
plot_line = fn code_list, title, value_title ->
  target_df =
    expenses_df
    |> DataFrame.filter(cat01_code in ^code_list and cat02_code == 11)
    |> DataFrame.select(["時間軸（年次）", "value", "品目分類（2020年改定）"])
    |> DataFrame.rename(["年", value_title, "品目分類"])

  year = Series.to_list(target_df["年"])
  expenses = Series.to_list(target_df[value_title])
  category = Series.to_list(target_df["品目分類"])

  VegaLite.new(width: 600, title: title)
  |> VegaLite.data_from_values(x: year, y: expenses, color: category)
  |> VegaLite.mark(:line, tooltip: true)
  |> VegaLite.encode_field(:x, "x", type: :ordinal, title: "年")
  |> VegaLite.encode_field(:y, "y", type: :quantitative, title: value_title)
  |> VegaLite.encode_field(:color, "color", title: "品目分類")
end
```

### 総支出

まず、総支出を見てみましょう

```elixir
plot_line.([1_100_000], "総支出額推移", "金額")
```

![e-stat_total.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/8a4435a1-383c-e936-cc82-5fadae489968.png)

総支出は徐々に下がっていき、コロナでガクッと下がって現在は上昇傾向にあります

### 世帯構成

次に世帯構成を見てみます

```elixir
plot_line.([300_000, 400_000, 500_000, 700_000], "世帯人員推移", "人数")
```

![e-stat_pop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/86125204-0d5c-0526-6115-aa745b88941a.png)

2007 年には１世帯あたり 2.54 人だったのが、 2022 年には 2.23 人まで減少しています

核家族化や単身世帯の増加が著しいことが分かります

有業人員はほぼ横ばいですが、６５歳以上が増え、１８歳未満は減っています

### 大分類毎支出金額

大分類を五つずつに分けて推移を見てみます

```elixir
[
  {"食料〜被服", Enum.to_list(10_000_000..50_000_000//10_000_000)},
  {"保健医療〜その他", Enum.to_list(60_000_000..100_000_000//10_000_000)}
]
|> Enum.map(fn {title, list} ->
  {
    title,
    plot_line.(list, "大分類毎支出金額推移", "金額")
  }
end)
|> Kino.Layout.tabs()
```

食料〜被服に関してはそこまで大きな変動はなさそうです

![e-stat_eat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/35439006-2f69-c001-206a-c29000fca549.png)

「その他」は明確に減っていっています

また、「交通・通信」、「教養・娯楽」、「その他」は 2020年に明らかにガクッと下がっていて、コロナの影響が明白です

![e-stat-others.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/daa411bf-5d0f-0780-546f-ef4d859b060c.png)

あとはいくつか気になる品目を見ていきましょう

### 米・パン・麺

```elixir
plot_line.([10_110_001, 10_120_000, 10_130_000], "支出額推移", "金額")
```

![e-stat-rice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c278f243-101a-8cc4-e919-86a42b136165.png)

みんな米を食べなくなっています

ちなみに私は毎食米です

### 魚・肉・野菜・菓子

```elixir
plot_line.([10_211_000, 10_310_000, 10_510_000, 10_800_000], "支出額推移", "金額")
```

![e-stat-meat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/3ecc5f8a-06ee-5d3e-e740-6411ec3f6d81.png)

お菓子が増えて魚が減っています

みんなもっとブリの照り焼きとか食べましょう

### 和服・洋服・履物

```elixir
plot_line.([50_100_000, 50_200_000, 50_700_000], "支出額推移", "金額")
```

![e-stat-cloth.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/07d76e3a-2b19-aa06-908c-1620d205d806.png)

みんな服にお金をかけないようになっています

履物も多少コロナの影響が出ていますね

### 背広・婦人服・子供服・ネクタイ

```elixir
plot_line.([50_210_010, 50_220_010, 50_230_010, 50_600_020], "支出額推移", "金額")
```

![e-stat-suit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/cf236a06-4a3a-899a-8318-d7376401d3b7.png)

2012年から復調の兆しを見せていたスーツは明らかにコロナで大打撃を被っています

ネクタイはコロナ関係なしに滅亡しそうです

### 郵便・固定電話・携帯電話

```elixir
plot_line.([70_300_010, 70_300_020, 70_300_030], "支出額推移", "金額")
```

![e-stat-tl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f02b0849-2d78-91a7-bb15-cb686e330827.png)

固定電話は衰退し、携帯電話は伸びていますが、2018年以降停滞し、2022年で落ちています

これは政府主導による携帯電話料金値下げの影響だと思われます

### テレビ・パソコン

```elixir
plot_line.([90_100_010, 90_100_050], "支出額推移", "金額")
```

![e-stat-tv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/3593e94a-e097-5967-789f-2d29cc33af4b.png)

薄型テレビの価格高騰で 2010 年ピークを迎えたテレビですが、 2011 年震災の影響で液晶テレビの価格が暴落、その後、パソコンよりも下のままです

コロナの影響で自宅に居る時間が増え、パソコンはむしろ支出が増加しています

### 新聞・雑誌・書籍

```elixir
plot_line.([90_300_010, 90_300_020, 90_300_030], "支出額推移", "金額")
```

![e-stat_book.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/686ecdbc-0f1b-e74b-2cc4-e1a4c8f4e976.png)

新聞、雑誌の衰退が止まりません

書籍は何とか下げ止まっているようです

## 特徴の強い品目の抽出

ここまでのように狙い撃ちで特定の品目を見ていくのも面白いですが、逆に推移の特徴が強い品目を統計的に抽出してみましょう

### 変動

変動の大きさ、つまり値のバラ付き具合は分散や標準偏差で見ることができます

ただし、分散や標準偏差では、元の価格帯が違うものを比較できません

次の例だと、どちらの変動が大きいと言えるでしょうか

- 10,000 円のものが 1,000 円値上げされる
- 1,000 円のものが 500 円値上げされる

絶対値では前者の方が変化が大きいですが、元の値段に対する割合では後者の方が大きいです

というわけで、変動の大きさを比較する場合、変動係数というものを用います

$ 変動係数 = \dfrac{標準偏差}{平均} $

各品目分類について変動係数を求めます

```elixir
summary_df =
  expenses_df
  |> DataFrame.filter(
    cat02_code == 11 and
      cat01_code <= 100_000_000 and
      unit == "円"
  )
  |> DataFrame.group_by(["cat01_code", "品目分類（2020年改定）"])
  |> DataFrame.summarise(
    変動係数: standard_deviation(value) / mean(value)
  )
  |> DataFrame.rename(["品目分類", "品目分類名", "変動係数"])

Kino.DataTable.new(summary_df)
```

#### 変動の小さいもの

変動の小さいもの上位５分類を出してみます

```elixir
summary_df
|> DataFrame.arrange(asc: 変動係数)
|> DataFrame.slice(0..4)
|> DataFrame.to_rows()
|> Enum.map(fn row ->
  title =
    row["品目分類名"]
    |> String.split(" ")
    |> Enum.at(1)

  {
    title,
    plot_line.([row["品目分類"]], "#{title}支出額推移", "金額")
  }
end)
|> Kino.Layout.tabs()
```

![low_change.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/a4529200-2f41-b8ff-49a6-2d673a2ef4ae.gif)

どれもほとんど動いていませんね

水道料金はそんなに変わらないようです

#### 変動の大きいもの

次は変動の大きいものを見てみましょう

```elixir
summary_df
|> DataFrame.arrange(desc: 変動係数)
|> DataFrame.slice(0..4)
|> DataFrame.to_rows()
|> Enum.map(fn row ->
  title =
    row["品目分類名"]
    |> String.split(" ")
    |> Enum.at(1)

  {
    title,
    plot_line.([row["品目分類"]], "#{title}支出額推移", "金額")
  }
end)
|> Kino.Layout.tabs()
```

![high_change.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/282716e8-2b93-0555-949a-d90ceb675d1d.gif)

「他のスポーツ施設使用料」に関しては、おそらく 2010 年の品目分類見直しによる影響だと思われます

テレビやゲーム機、ビデオレコーダーは価格の暴落ですね

出産入院料は 2009 年以降の様々な子育て支援政策による負担減ではないかと思います（単純な少子化の影響にしては明確に 2009年、 2010年に下がっているので）

### 単調変化

特定のイベントによる急激な変化ではなく、単純に毎年増加したり減少したりしているものを抽出してみます

まず、（私が勝手に定義した値ですが）経年変化率を計算します

$ 経年変化率 = \dfrac{支出金額_{2022} - 支出金額_{2007}}{支出金額_{2007}} $

($ 支出金額_{y} $ は y 年の支出金額とします)

```elixir
change_df =
  expenses_df
  |> DataFrame.filter(
    cat02_code == 11 and
      cat01_code <= 100_000_000 and
      unit == "円"
  )
  |> DataFrame.select(["世帯区分", "cat01_code", "品目分類（2020年改定）", "時間軸（年次）", "value", "unit"])
  |> DataFrame.rename(["世帯区分", "品目分類", "品目分類名", "年", "金額", "単位"])
  # 年毎の金額を列に展開する
  |> DataFrame.pivot_wider("年", "金額")
  # 16年間存在した品目分類だけを対象にする
  |> DataFrame.filter(
    not contains(品目分類名, "までは") and
      "2007年" |> col() |> is_not_nil()
  )
  |> DataFrame.mutate(経年変化率: (col("2022年") - col("2007年")) / col("2007年"))
  |> DataFrame.arrange(desc: 経年変化率)

Kino.DataTable.new(change_df)
```

この経年変化率だけでは単年の急激な変化も含まれているため、最大の単年変化率も計算します

$ 単年変化率_y = \dfrac{支出金額_y - 支出金額_{y-1}}{支出金額_{y-i}} $

また、以下のような単調変化係数を定義します

$ 単調変化係数 = \dfrac{経年変化率}{|最大単年変化率|} $

```elixir
single_change_df =
  2008..2022
  |> Enum.reduce(change_df, fn year, acc_df ->
    this_year = "#{year}年"
    before_year = "#{year - 1}年"
    col_name = "単年変化率_#{this_year}" |> String.to_atom()

    acc_df
    |> DataFrame.mutate([
      {
        ^col_name,
        (col(^this_year) - col(^before_year)) / col(^before_year)
      }
    ])
  end)
  |> DataFrame.pivot_longer(&String.starts_with?(&1, "単年変化率_"),
    names_to: "変化年",
    values_to: "単年変化率"
  )
  |> DataFrame.group_by("品目分類")
  |> DataFrame.summarise(最大単年変化率: max(単年変化率))

change_df =
  change_df
  |> DataFrame.join(single_change_df)
  |> DataFrame.mutate(
    単調変化係数: 経年変化率 / (最大単年変化率 |> pow(2) |> pow(0.5))
  )

Kino.DataTable.new(change_df)
```

#### 単調減少

単調変化係数が負の値のとき、単調減少しています

```elixir
change_df
|> DataFrame.arrange(asc: 単調変化係数)
|> DataFrame.slice(0..4)
|> DataFrame.to_rows()
|> Enum.map(fn row ->
  title =
    row["品目分類名"]
    |> String.split(" ")
    |> Enum.at(1)

  {
    title,
    plot_line.([row["品目分類"]], "#{title}支出額推移", "金額")
  }
end)
|> Kino.Layout.tabs()
```

![simple_minus.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/6bd8f7f0-7fc7-f60b-b514-13b1e210686f.gif)

固定電話や書籍、新聞はコロナの影響などとは関係なしに、単純に毎年減少しています

つまり、今後コロナの影響が少なくなっても回復の見込みはなさそうです

#### 単調増加

逆に単調増加している品目分類です

![simple_plus.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/19e7d5bf-504d-8546-996b-526a4fc78c6b.gif)

炭酸飲料や調理食品、チョコレート菓子、サラダなどは順調に毎年増加しています

つまりどんどん日常的に消費されるようになっている、ということです

### 新型コロナによる影響

新型コロナが発生した 2020 年前後での変化率を算出し、新型コロナによる影響の大きかった品目分類を抽出します

```elixir
covid_df =
  expenses_df
  |> DataFrame.filter(
    cat02_code == 11 and
      cat01_code <= 100_000_000 and
      unit == "円" and
      time_code >= 2_019_000_000
  )
  |> DataFrame.select(["cat01_code", "品目分類（2020年改定）", "時間軸（年次）", "value"])
  |> DataFrame.rename(["品目分類", "品目分類名", "年", "金額"])
  |> DataFrame.filter(not contains(品目分類名, "までは"))
  |> DataFrame.pivot_wider("年", "金額")
  |> DataFrame.mutate(
    コロナ発生時変化率: (col("2020年") - col("2019年")) / col("2019年"),
    コロナ発生後変化率: (col("2022年") - col("2020年")) / col("2019年")
  )

Kino.DataTable.new(covid_df)
```

#### コロナによる悪影響の強いもの

```elixir
covid_df
|> DataFrame.arrange(asc: コロナ発生時変化率)
|> DataFrame.slice(0..4)
|> DataFrame.to_rows()
|> Enum.map(fn row ->
  title =
    row["品目分類名"]
    |> String.split(" ")
    |> Enum.at(1)

  {
    title,
    plot_line.([row["品目分類"]], "#{title}支出額推移", "金額")
  }
end)
|> Kino.Layout.tabs()
```

![covid_bad.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/ec249d32-9df9-8d2a-8b14-4e2bd9dff6a7.gif)

明らかに旅行業は大ダメージを負っています

#### コロナ後の回復が見えるもの

```elixir
covid_df
|> DataFrame.filter(コロナ発生時変化率 < -0.5 and コロナ発生後変化率 > 0.3)
|> DataFrame.arrange(desc: コロナ発生後変化率)
|> DataFrame.slice(0..4)
|> DataFrame.to_rows()
|> Enum.map(fn row ->
  title =
    row["品目分類名"]
    |> String.split(" ")
    |> Enum.at(1)

  {
    title,
    plot_line.([row["品目分類"]], "#{title}支出額推移", "金額")
  }
end)
|> Kino.Layout.tabs()
```

![covit_v.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/47ca6c8e-9127-1d78-f848-e9d50d9c5b88.gif)

映画は鬼滅の刃や ONE PIECE の大ヒットで見事に V 字回復しています

#### コロナ後も回復していないもの

```elixir
covid_df
|> DataFrame.filter(コロナ発生時変化率 < -0.5 and コロナ発生後変化率 < 0.2)
|> DataFrame.arrange(asc: コロナ発生後変化率)
|> DataFrame.slice(0..4)
|> DataFrame.to_rows()
|> Enum.map(fn row ->
  title =
    row["品目分類名"]
    |> String.split(" ")
    |> Enum.at(1)

  {
    title,
    plot_line.([row["品目分類"]], "#{title}支出額推移", "金額")
  }
end)
|> Kino.Layout.tabs()
```

![covid_not_yet.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c3962f38-0e68-fe5e-1003-57ba97054550.gif)

海外旅行はまだまだ回復していません

一方、幼児用教育は 2019 年 10 月から「幼児教育の無償化」が実施されたために支出が減っているので、これは喜ばしいことです

## まとめ

家計支出を見ることで、色々な社会の動きが見えました

e-Stat には他にも様々なデータがあるので、これを分析していきたいですね