Rのpivot_longerとpivot_wider

R

初稿;2026/09/21(月)

はじめに

いつもこのpivot_wider()とpivot_longer()は全部忘れていて、必要になる度に大混乱してしまう。ということで、ここにメモっておくことにする。

R
library(tidyverse)
R

使用するデータ

オリジナルのデータとして、以下の画像のような形のデータフレームを使用する。これは、使った動物の数を6匹とし、それらに各濃度(列dose)の薬剤CPDと対照となるDMSO(列drug)を投与し、その24及び48時間後(列time)の腫瘍、血液、肝臓、肺、脾臓、脳中の薬剤濃度(列concentration)を測定して得られた結果を想定した。

統計処理を考える場合、得られるデータは縦型(longer)になっているはずである。なので、ここで設定したオリジナルのデータフレームも縦型とする。データベースってのは、基本的に1変数(列)に対して1種類の値が入っている。そうしなければすぐに解析を始めることが出来ず、データクリーニングに膨大な時間を費やす羽目になる。

以下が上記のデータを生成するコードである。値はrnorm()を使用する。最後に、24時間後と48時間後毎の各組織中濃度として、データフレームanalysis_24とanalysis_48hを作成する。ここでのポイントは列order_tissueである。これは、各マウスで、tumor、blood、liver、lung、screen、brainにそれぞれ1から6までの番号を付けた列である。他に重要なものとしては、列orderである。これを昇順に並べれば、必ず元のデータの並びに戻ることが出来る。そういった、言わばメインキーになる番号は、どんなデータでも付けておかなければならない。以下のコードを流せば、表が得られる。

R
# Original data probably longer format.
# tumor, blood, liver, lung, spleen, brain, 6 tissues
# 1, 3, 10, 30, 100, 5 doses
# N = 6 in each group

original_24h <- data.frame(
  drug = c(rep("CPD", each = 6*6, times = 5), rep("DMSO", each = 6*6, times = 1)),
  dose = c(
    rep(c("1mg/kg", "3mg/kg", "10mg/kg", "30mg/kg", "100mg/kg"),
        each = 6*6,
        times = 1),
    rep("0mg/kg",
        each = 6*6,
        times = 1)),
  tissue = c(
    rep(c("tumor", "blood", "liver", "lung", "spleen", "brain"),
        each = 1,
        times = 6*5),
    rep(c("tumor", "blood", "liver", "lung", "spleen", "brain"),
        each = 1,
        times = 6*1)),
  concentration = c(
    rnorm(6*5*6, mean = 20, sd = 4),
    rnorm(6*1*6, mean = 0.5, sd = 0.1)),
  time = "24h"
)

original_48h <- data.frame(
  drug = c(rep("CPD", each = 6*6, times = 5), rep("DMSO", each = 6*6, times = 1)),
  dose = c(
    rep(c("1mg/kg", "3mg/kg", "10mg/kg", "30mg/kg", "100mg/kg"),
        each = 6*6,
        times = 1),
    rep("0mg/kg",
        each = 6*6,
        times = 1)),
  tissue = c(
    rep(c("tumor", "blood", "liver", "lung", "spleen", "brain"),
        each = 1,
        times = 6*5),
    rep(c("tumor", "blood", "liver", "lung", "spleen", "brain"),
        each = 1,
        times = 6*1)),
  concentration = c(
    rnorm(6*5*6, mean = 20, sd = 4),
    rnorm(6*1*6, mean = 0.5, sd = 0.1)),
  time = "48h"
)

original <- rbind(original_24h, original_48h)

table(original$tissue)
# blood  brain  liver   lung spleen  tumor 
#     72     72     72     72     72     72 
table(original$drug)
 # CPD DMSO 
 # 360   72 
table(original$dose)
  # 0mg/kg 100mg/kg  10mg/kg   1mg/kg  30mg/kg   3mg/kg 
  #     72       72       72       72       72       72 

original <- original %>% mutate(order = 1:nrow(original),
                                animal = "Ms",
                                order_tissue = rep(c(1:6), times = 72),
                                order_animal = rep(c(1:6), each =6, times=12))


original <- original %>% unite(col = sample_id, sep="_", animal, order, remove = FALSE)
original <- original %>% unite(col = tissue_id, sep="_", tissue, order_animal, remove = FALSE)
original <- original %>% unite(col = drug_dose, sep="_", drug, dose, remove=FALSE)

analysis_24h <- original %>% filter(time == "24h")
analysis_48h <- original %>% filter(time == "48h")
R
これが使用するデータフレーム。

pivot_wider

ここでは、最初に列tissueにある値、すなわち、tumor、blood、liver、lung、spleen、brainの値をリストwider_24hの各要素に入れ、それらに対してpivot_wider()を使っていく。pivot_wider()でよく使う引数は以下の表の通りである。

引数説明
id_cols =残す列。ここでは列tissue_idを使用した。値は全部異なっている必要がある。
names_from=横方向に伸ばす列。ここでは列drug_doseを使用した。
value_from=横方向に伸ばした列に入れる値を保持した列。ここは列concentrationを使用した。
pivot_wider()で基本的に必要な引数

リストwider_24hの各要素はbind_rows()で結合し、最後に、24時間後と48時間後のデータをさらにbind_rows()で結合する。

R

# longer to Wider

#24h
wider_24h <- list()
tissue_name <- names(table(analysis_24h$tissue))
for(i in 1:length(tissue_name)){
  wider_24h[[i]] <- analysis_24h %>% filter(tissue == tissue_name[i]) %>% 
    pivot_wider(id_cols = tissue_id,
                names_from = drug_dose,
                values_from = concentration)
    
}

wider_24h_df <- bind_rows(wider_24h) %>% mutate(time = "24h")

# 48h
wider_48h <- list()
tissue_name <- names(table(analysis_48h$tissue))
for(i in 1:length(tissue_name)){
  wider_48h[[i]] <- analysis_48h %>% filter(tissue == tissue_name[i]) %>% 
    pivot_wider(id_cols = tissue_id,
                names_from = drug_dose,
                values_from = concentration)
    
}
wider_48h_df <- bind_rows(wider_48h) %>% mutate(time = "48h")

wider <- bind_rows(wider_24h_df, wider_48h_df)
R

以下のデータフレームが出来上がる。

pivot_wider()で作成したデータフレームwider。

pivot_widerでついでに平均値と標準偏差を計算する

pivot_wider()の引数values_fnには関数を入れることが出来る。この場合、mean()とsd()を入れてしまえば、マウス毎の各臓器の平均値と標準偏差が計算出来る。上記のpivot_wider()との違いは、引数id_colsに入れる列が違うことである。平均値や標準偏差、もしくは他の関数で複数の値をまとめてしまう場合、引数id_cols=の値は重複していても大丈夫である。代表値や統計量の一つにまとまってしまうためである。

引数説明
id_cols =残す列。ここでは列tissueを使用した。引数values_fnを使って代表値にしてしまう場合、各臓器毎に番号を振ったるする必要がない。
names_from=横方向に伸ばす列。ここでは列drug_doseを使用した。
value_from=横方向に伸ばした列に入れる値を保持した列。ここは列concentrationを使用した。
values_fn=Rに組み込まれている関数や自作の関数名を入れることが出来る。ここではmean()とsd()を使用した。
pivot_wider()で基本的に必要な引数
R

# calculate mean and sd
wider_24h_mean <- analysis_24h %>% pivot_wider(id_cols = tissue,
                                  names_from = dose,
                                  values_from = concentration,
                                  values_fn = mean) %>% mutate(stats = "mean", time = "24h")
wider_24h_mean <- wider_24h_mean %>% unite(col = tissue_id, sep="_", tissue, stats)

wider_24h_sd <- analysis_24h %>% pivot_wider(id_cols = tissue,
                                  names_from = dose,
                                  values_from = concentration,
                                  values_fn = sd) %>% mutate(stats = "sd", time = "24h")
wider_24h_sd <- wider_24h_sd %>% unite(col = tissue_id, sep="_", tissue, stats)


wider_48h_mean <- analysis_48h %>% pivot_wider(id_cols = tissue,
                                  names_from = dose,
                                  values_from = concentration,
                                  values_fn = mean) %>% mutate(stats = "mean", time = "48h")
wider_48h_mean <- wider_48h_mean %>% unite(col = tissue_id, sep="_", tissue, stats)


wider_48h_sd <- analysis_48h %>% pivot_wider(id_cols = tissue,
                                  names_from = dose,
                                  values_from = concentration,
                                  values_fn = sd) %>% mutate(stats = "sd", time = "48h")
wider_48h_sd <- wider_48h_sd %>% unite(col = tissue_id, sep="_", tissue, stats)

wider_stats <- bind_rows(wider_24h_mean, wider_24h_sd, wider_48h_mean, wider_48h_sd)
R

以下のデータフレームが出来上がる。

pivot_wider()で平均値と標準偏差を計算したデータフレームwider_stats。

pivot_longer

基本的に、1変数(列)に入る値は1種類とすれば、縦型になる。ただし、エクセルしか使ってこなかった輩は、大抵自由フォーマットで、自分しかわからないような、汎用性の全くないフォーマットを作成してくる。この場合は、最初にpivot_longer()を使ったり、pivit_wider()を使ったりしながら作業を進める必要がある。

特に数十列くらいのデータだったら別にエクセルでもいいのだが、これが数百、数千列あったらどうするつもりだろうか。絶対手作業は無理である。

以下の表にpivot_longer()の引数をまとめる。ここで最初に使用するデータフレームwider_24h_2は、pivot_wider()で作成したデータフレームwiderから、24時間後の値を抽出したデータフレームである。

引数説明
cols =縦にする値の範囲(基本的に列)。ここでは2:7、すなわち、データフレームwider_24h_2の、2列目から7列目を指定した。
names_to =新しく作成する列。ここでは列doseを新しく作成する。
values_to =新しく作成する列に入れる値が入っている列。ここでは列concentrationを入れる。

以下の表がデータフレームwider_24h_2である。データフレームwider_48h_2はデータフレームwiderから、列timeが48hのものを抽出して作成したものである。

pivot_longer()で変換するデータフレームwider_48h_2。
R
# Wider to longer

# 24h

wider_24h_2 <- wider %>% filter(time == "24h")
wider_24h_2 <- wider_24h_2 %>% mutate(tissue = gsub(pattern = "_\\d+$", replacement = "", x = tissue_id))
wider_24h_2[,2:7] <- wider_24h_2[,2:7]  %>% apply(MARGIN = 2, FUN = as.numeric)
wider_24h_2 <- wider_24h_2 %>% as_tibble()

longer_24h <- wider_24h_2 %>% pivot_longer(cols = 2:7, names_to = "dose", values_to = "concentration")
longer_24h <- longer_24h %>% mutate(dose = gsub(pattern = "^.*_", replacement = "", x=dose))


# 48h

wider_48h_2 <- wider %>% filter(time == "48h")
wider_48h_2 <- wider_48h_2 %>% mutate(tissue = gsub(pattern = "_\\d+$", replacement = "", x = tissue_id))
wider_48h_2[,2:7] <- wider_48h_2[,2:7]  %>% apply(MARGIN = 2, FUN = as.numeric)
wider_48h_2 <- wider_48h_2 %>% as_tibble()

longer_48h <- wider_48h_2 %>% pivot_longer(cols = 2:7, names_to = "dose", values_to = "concentration")
longer_48h <- longer_48h %>% mutate(dose = gsub(pattern = "^.*_", replacement = "", x=dose))

longer <- bind_rows(longer_24h, longer_48h)
R
pivot_longer()で変換して得られたデータフレームlonger。

まとめ

これで、必要になったときにこの記事を見れば、データフレームを整えるのも多少は捗るに違いない。多分。