セルの値を複数行・複数列に展開する

# はじめに

セルの値を区切り文字で分離して複数行に展開する`tidyr::separate_longer_delim()`関数や複数列に展開する`tidyr::separate_wider_delim()`関数を使う機会があったので，忘れないうちにメモしておきます．

# データ

東京電力ホールディングスのWebサイトに[数表で見る東京電力](https://www.tepco.co.jp/about/fact_database/)というページがあり，そこでは東京電力に関わる色々なデータをダウンロードすることができます．
今回は[平均モデルの電気料金](https://www.tepco.co.jp/corporateinfo/illustrated/charge/average-past-j.html)のデータをダウンロードし，このページにあるグラフを再現したいと思います．

早速データをダウンロードします．

![2024-02-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131152/0cfe90ce-20f6-c4ac-3e8c-4539e418c575.png)

ダウンロードした`6241_change-001.csv`を適当なディレクトリに移動し，RStudioでインポートしてみます．

![2024-02-06 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131152/4d633108-e6a5-2b4f-7735-ae05960969b2.png)

2行スキップすれば読み込めそうです．縦横は後で入れ替えることにします．

![2024-02-06 (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131152/a7a32238-3d1f-c38a-fb0e-300ee316fc5f.png)

ということで`df_charge`というデータフレームに読み込むことにします．

```r
library(tidyverse)

X6241_charge_001 <- read_csv("6241_charge-001.csv", skip = 2)
df_charge <- as_tibble(
  cbind(
    適用期間 = names(X6241_charge_001)[-1],
    電気料金 = t(X6241_charge_001)[-1]
    )
  ) |>
  mutate(電気料金 = as.numeric(電気料金))
```

できあがったデータフレームは四半期毎のデータのようです．

![2024-02-06 (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131152/286bc70d-9d19-d764-652d-872d9dfb4286.png)

ん？

![2024-02-06 (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131152/6c597797-a27b-ced5-dc2c-c48e3a0ded6e.png)

んんん？

![2024-02-06 (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131152/1006d99f-bee4-1242-711a-7be75ee5d509.png)

どうも四半期毎になっているのは2009年の第1四半期までで，それ以降は月次のデータとなっているようです[^時系列]．そこで，このデータフレーム全体を月次データとして変換することにします．そのために，四半期毎になっている行については，電気料金の値を3ヶ月分複製した行を挿入します．

[^時系列]: よく見ると上の東京電力ホールディングスのページにあるグラフも横軸が等間隔にはなってないですね．

# 処理

つまり，
| 適用期間 | 電気料金 |
|:-|:-|
|2009/1～3|7206|

という行を

| 適用期間 | 電気料金 |
|:-|:-|
|2009/01|7206|
|2009/02|7206|
|2009/03|7206|

という3行に変換したいわけです．

下準備として適用期間の値を年と月を表す列に`/`で分割するために，`separate_wider_delim()`関数を使います．

```r
df_charge <- df_charge |>
  separate_wider_delim(
    適用期間,
    delim = "/",
    names = c("年", "月"),
    too_few = "align_start"
    )

df_charge
# A tibble: 189 × 3
   年    月     電気料金
   <chr> <chr>     <dbl>
 1 2004  4～6       6418
 2 2004  7～9       6418
 3 2004  10～12     6142
 4 2005  1～3       6200
 5 2005  4～6       6224
 6 2005  7～9       6203
 7 2005  10～12     6294
 8 2006  1～3       6429
 9 2006  4～6       6269
10 2006  7～9       6269
# ℹ 179 more rows
# ℹ Use `print(n = ...)` to see more rows
```

`too_few = "align_start"`は2つに分割できない場合（`Sep-22`のケース）に先頭から値を埋めて，残りに`NA`を入れることを指定しています．

```r
df_charge |>
  filter(年 == "Sep-22")
# A tibble: 1 × 3
  年     月    電気料金
  <chr>  <chr>    <dbl>
1 Sep-22 NA        6797
```

次に`7～9`などの四半期のラベルを，カンマ区切りの数字（`7,8,9`など）で置換し，2008年9月の値も処理します．

```r
df_charge <- df_charge |>
  mutate(
    月 = if_else(is.na(月), "9", 月),    
    月 = case_when(
      月 == "1～3" ~ "1,2,3",
      月 == "4～6" ~ "4,5,6",
      月 == "7～9" ~ "7,8,9",
      月 == "10～12" ~ "10,11,12",
      月 == "7～8" ~ "7,8",
      TRUE ~ 月
      ),
    年 = if_else(年 == "Sep-22", "2008", 年)
    )
```

この`月`列に対して`separate_longer_delim()`関数を使います．

```r
df_charge <- df_charge |>
  separate_longer_delim(月, delim = ",")

df_charge
# A tibble: 228 × 3
   年    月    電気料金
   <chr> <chr>    <dbl>
 1 2004  4         6418
 2 2004  5         6418
 3 2004  6         6418
 4 2004  7         6418
 5 2004  8         6418
 6 2004  9         6418
 7 2004  10        6142
 8 2004  11        6142
 9 2004  12        6142
10 2005  1         6200
# ℹ 218 more rows
# ℹ Use `print(n = ...)` to see more rows
```

データフレーム全体の行数も189行から228行に増えていることが分かります．

最後に折れ線グラフを描いて完成です．

```r
library(tsibble)

df_charge |>
  mutate(年月 = make_yearmonth(year = 年, month = 月)) |>
  select(年月, 電気料金) |>
  as_tsibble(index = 年月) |>
  ggplot(aes(x = 年月, y = 電気料金)) +
  geom_point() + geom_line()
```

![Rplot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131152/2a64ebf4-bba3-642e-f92a-4229ee3e9404.png)

# 終わりに

今回のケースであれば欠落データはなさそうですので，`tidyr::uncount()`関数を使って行をコピーしてから，別途作成したインデックスと組み合わせても実現できると思います．

それにしてもどうやったらこんなヘンテコなデータができるんでしょうかね？