須通り
Sudo Masaaki official site
For the reinstatement of
population ecology.

おまえが長くtidyverseを覗くならば、tidyverseもまた等しくおまえを見返すのだ。

     

ホーム | 統計 Top | Tidyverseによるデータフレーム加工(01)tidyr による表の変形

それなりに大きなデータセットの解析を始める時、とりあえずRに放り込んでデータフレームにすることで、可視化や統計処理の各種関数に放り込む準備が整う。問題は誰かから貰った、あるいは自分で入力した表形式のデータが、複雑な構造をしており直ちに解析を始められない場合だ。小さければスプレッドシートの段階で手動修正してもよい。
すごく大きなデータだったり、フォーマットは揃っているが表の枚数が多すぎて手作業で直せないときはどうする? tidyr の出番である。

目次

  1. Tidyverse 以前:tidy data とデータクリーニング
    • データが tidy であるということ
  2. データフレームの long <-> short 形式を相互変換する gather と spread
    • データフレームの列を集約して long 形式にする tidyr::gather 関数
    • データフレームの列の内容を展開して wide 形式にする tidyr::spread 関数
  3. データ列ラベルを結合する unite と分割する separate / extract
    • データ列ラベルを分割する tidyr::separate 関数
    • データ列ラベルを高度な正規分割表現に従って分割する tidyr::extract 関数
    • データ列ラベルを単純結合する tidyr::unite 関数

本記事の続編 「Tidyverseによるデータフレーム加工(02)dplyr::*_join による複数データフレームの結合/欠損値補完/ルックアップ」 で、 dplyr::*_join 関数ファミリーを用いて複数の tidy なデータフレームの合意データを作る方法を解説しています。

Tidyverse 以前:tidy data とデータクリーニング

はじめに "tidyverse" とは、R界の木下藤吉郎こと Hadley Wickham と愉快な仲間たちによって構築が進む、Rを拡張する一連のパッケージ群である。データサイエンス界隈で数年前から耳にする "ggplot2" とか "dplyr" とかいったツールは、それぞれ独立したパッケージとして誕生したものであるが、現在は tidyverse における描画やデータフレーム加工等の機能を分担する関数群として理解されるようになっている。そして本ページで取り上げる tidyr は、基本的には1枚のデータフレームとして与えられた表形式データを対象として、行や列の配置を、解析に便利な形へとアレンジするための関数群だ。

Tidyverse の "tidy" は「整然とした」といった意味を持つ英単語である。いったい何が整然としているのか。幸いにも「整然データとは何か」という精緻な日本語解説がある。棒引きすると、tidy data = 整然データとは以下の4つの特徴を備えた表形式データである。

  1. 個々の変数 (variable) が1つの列 (column) をなす。
  2. 個々の観測 (observation) が1つの行 (row) をなす。
  3. 個々の観測の構成単位の類型 (type of observational unit) が1つの表 (table) をなす。
  4. 個々の値 (value) が1つのセル (cell) をなす。

データが tidy であるということ

実例を示すのが分かり易いだろう。これから用いるデータは、架空の殺虫剤試験において、Site 1から6までのほ場に薬剤処理区(Treat)と対照区(Control)を設置し、薬剤処理の前(Before)と後(After)の各時期、一定面積の区画にいる害虫数を数えたものである。数値自体はポアソン分布に従う乱数としてでっちあげた。なおこの最初の表は tidy ではないので注意。

Tidy「ではない」表データ。
 AugustSeptember
Site TreatedControl TreatedControl
 BeforeAfterBeforeAfterBeforeAfterBeforeAfter
164411123
226505213
341503043
416302015
525400227
62NA815145

たぶん、この入力方法でも「かなり上等」な部類であろう。大きな声では言えないが、実際の薬剤試験の報告書はもっと雑然としたテーブルを含んでいる。さて上記の表、このままでは R にインポートすらできない。ヘッダー部分のセルが結合されているためである。とりあえず手作業で修正してやったものが以下の tidypest01.csv データだ。


# だいぶマシになったが、厳密にはまだ tidy ではない R データフレームの例
tidypest.raw <- read.table("tidypest01.csv", header=TRUE, sep="," , as.is=TRUE ) # as.is

> tidypest.raw
      Season Site Treat.Before Treat.After Control.Before Control.After
1     August    1            6           4              4             1
2     August    2            2           6              5             0
3     August    3            4           1              5             0
4     August    4            1           6              3             0
5     August    5            2           5              4             0
6     August    6            2          NA              8             1
7  September    1            1           1              2             3
8  September    2            5           2              1             3
9  September    3            3           0              4             3
10 September    4            2           0              1             5
11 September    5            0           2              2             7
12 September    6            5           1              4             5

手動での主な修正点として、調査シーズン(August or September)の各列を横に並べるのではなく、同じ列内の別行に入るように格納し直した。またヘッダーが複数行になるとコンピュータでの扱いが難しいため、"Treat.Before" などとして「試験処理」と「時間」の列ラベルを繋いである(なおコンマ,やハイフン-で分けるとRへの取り込み時に列ラベルの分割点として認識されてしまうので、ピリオド.かアンダーバー_を使おう)。

このデータ tidypest.raw は Hadley が言うところの "tidy" の条件を、部分的には満たしている。ただし、"Treat.Before" などは「2つの説明変数(要因)を無理やり1つの列名として繋げた」ものであり、この状況は、最初に述べた tidy data の1番目の要件から外れる。つまり完全な tidy data ではない。

データフレームの long <-> short 形式を相互変換する gather と spread

さて R 環境に取り込めたはいいものの、tidypest.raw の表形式のままではデータの表示や加工が容易でない。まずはこれを、以下のような縦長の表構造に変形することを目指す。


      Season Site       TempKey1 TempValue
1     August    1   Treat.Before         6
2     August    2   Treat.Before         2
3     August    3   Treat.Before         4
4     August    4   Treat.Before         1
5     August    5   Treat.Before         2
6     August    6   Treat.Before         2
7  September    1   Treat.Before         1
8  September    2   Treat.Before         5
9  September    3   Treat.Before         3
10 September    4   Treat.Before         2
11 September    5   Treat.Before         0
12 September    6   Treat.Before         5
13    August    1    Treat.After         4
14    August    2    Treat.After         6
15    August    3    Treat.After         1
16    August    4    Treat.After         6
17    August    5    Treat.After         5
18    August    6    Treat.After        NA
19 September    1    Treat.After         1
20 September    2    Treat.After         2
21 September    3    Treat.After         0
22 September    4    Treat.After         0
23 September    5    Treat.After         2
24 September    6    Treat.After         1
25    August    1 Control.Before         4
26    August    2 Control.Before         5
27    August    3 Control.Before         5
28    August    4 Control.Before         3
29    August    5 Control.Before         4
30    August    6 Control.Before         8
31 September    1 Control.Before         2
32 September    2 Control.Before         1
33 September    3 Control.Before         4
34 September    4 Control.Before         1
35 September    5 Control.Before         2
36 September    6 Control.Before         4
37    August    1  Control.After         1
38    August    2  Control.After         0
39    August    3  Control.After         0
40    August    4  Control.After         0
41    August    5  Control.After         0
42    August    6  Control.After         1
43 September    1  Control.After         3
44 September    2  Control.After         3
45 September    3  Control.After         3
46 September    4  Control.After         5
47 September    5  Control.After         7
48 September    6  Control.After         5

上記 tidypest.raw.long1$TempValue のように、観測データ(observation: 統計モデルに当てはめるならば目的変数に該当)が単一の列として格納される表構造を、データサイエンス界隈では long 形式と呼ぶ。これに対して tidypest.raw の 列 c("Treat.Before", "Treat.After", "Control.Before", "Control.After") のように、観測データが複数の列に分けて記載されているような表構造を、wide 形式という。ここでようやく、tidyverseパッケージの出番となる。

データフレームの列を集約して long 形式にする tidyr::gather 関数


# 上記の変換を行うコード
# install.packages('tidyverse', dependencies=TRUE) # 初回ならば要インストール
library(tidyverse) # load the package of "tidyverse"
# wide -> long には tidyverse:gather を使う。
tidypest.raw.long1 <- tidypest.raw %>%
    tidyr::gather(  key=TempKey1, value=TempValue,
                    Treat.Before, Treat.After, Control.Before, Control.After,
                    na.rm=FALSE, convert=FALSE, factor_key=FALSE )
tidypest.raw.long1

上記処理により、tidypest.raw に存在していた Treat.Before, Treat.After, Control.Before, Control.After という4列分のデータの中身が、新しい列 "TempValue" として一繋がりに格納される。それだけでは元々存在していた4つのうち、どの列から取り出されたデータなのかが判らないため、"TempKey1" という列を新たに作り、出自に相当する列の名前を文字列情報として格納している。


gather(data, key, value, ..., na.rm = FALSE, convert = FALSE, factor_key = FALSE)

省略不可な引数
data    処理対象のデータフレーム。
key    変換後のデータフレームに新規生成すべき key 列(=カテゴリのこと)の名前を指定。引用符で囲む必要はない。
value    変換後のデータフレームに新規生成すべき value 列(=データの値)の名前を指定。引用符で囲む必要はない。
...    変換前のデータフレームにある、Long形式にまとめてしまいたい列の名を(引用符なしで)1つ以上指定。
    変換後のデータフレームにおいて、指定された列の列名が key 列に格納され、データが value 列に格納される。
    変換前のデータフレームにある x から z までの間の全列を含めたい場合は x:z などとする。
    列 y を除外したければ -y などとする。さらなる詳細は dplyr::select() のドキュメントを参照。

オプション扱いの引数
na.rm    TRUE であるとき、value列が NA となるデータ行がもしあれば削除される。
convert    TRUEであるとき、 key 列に含まれるデータを自動で型変換する。元の列名が numeric, integer, or logical であるときに便利。
factor_key    デフォルトはFALSEで、key列の値は文字列ベクトルで格納される。TRUEであるとき、factor型に変換される。このとき元のデータフレームにおける列の順序が保存される。

ちなみに tidyr::gather という記法だが、「tidyr」というパッケージに含まれる「gather」という名前の関数を名指しで呼び出している。もしRの他のパッケージに、たまたまgatherという名前の関数があっても混同しないで済む。詳しくは「名前空間」で検索検索。

パイプ演算子 %>% のこと

なお上記の操作、通常のRでの演算と比べて癖があるので注意。最たるものは %>% という謎の演算子。そしてgather() 関数の第一引数に必須なはずの data が使われていない?この %>% は「パイプ演算子」といって、現在は tidyverse に統合済みの magrittr というパッケージにて提供されている(なおパイプ自体はR言語に限った機能ではない)。簡単にいうと、パイプ演算子の左辺にある tidypest.raw というデータを、右辺こと gather() 関数の第一引数 data に代入して実行しませい、という指示を出す。さらに <- 演算子が控えているから、gather() の実行結果は最終的には左辺の tidypest.raw.long1 という新しいオブジェクトに格納される。

蛇足:Rの計算処理は別にパイプ演算子を使わなくても書ける。というか処理関数が一つだけなら全くメリットはない。が、通常の代入 <- 演算子は必ず、左辺のオブジェクトに計算結果のデータを書き込む。これだと単一のデータに複数の関数を直列で適用し、最終結果だけを保存したい(=パイプライン処理)場合、メモリを無駄に消費してしまう。関数をネストする書き方(hoge <- exp(sum(c(1,2,3))) とか)でも対処できるが、階層が深くなるとコードの可読性が著しく下がり好ましくない。

データフレームの列の内容を展開して wide 形式にする tidyr::spread 関数

「列を集約」して long にする処理が tidyr::gather であった。その逆として tidyr::spread で「列の内容を展開して」wide に変形できると覚えておこう。


# 上記の逆変換を行うコード
# long -> wide には tidyverse::spread を使う。
tidypest.raw.inv <- tidypest.raw.long1 %>%
    tidyr::spread(  key=TempKey1, value=TempValue, fill="あらへん。" )
tidypest.raw.inv

spread(data, key, value, fill = NA, convert = FALSE, drop = TRUE, sep = NULL)

省略不可な引数
data    データフレーム。
key     分類のキーとして用いる。元データに含まれる列を名前で指定。引用符で囲む必要はない。ここに格納されている値の水準数だけ、新しいデータ列が作られ、そこにvalueで指定したデータが格納される。
value    上記で分類したセルをpopulateするためのvalue。元データに含まれる列を名前で指定。引用符で囲む必要はない。

オプションの引数
fill    変換後のカテゴリに対応するデータ行が存在しないとき、ここに指定した値で置き換える。なお元データの該当行にNAが入っている場合も、ここで指定した値に置換される。
convert    TRUEであるとき、分類の結果生成された各列に対して、type.convert() 関数が as.is=TRUE として実行される。元の列に含まれている各変数が、複数のデータ型を文字列にまとめたものである場合にとりわけ有用。もし元のクラスがfactorないしdateであった場合、新たに生成される列は自動では同じ型として保存されないので、いったんcharacterにまとめてから変換する必要がある。
drop    FALSEであるとき、新しく生成されるfactor型のデータ列において存在しない水準は削除される。
sep    NULLであるとき、新たに生成される列の名前を key 列に格納されている変数の値そのものとして生成する。NULL以外であれば、<key_name><sep><key_value> という命名規則で作られる。

上記処理により、data=tidypest.raw.long1 に存在していた value=TempValue という列のデータの中身が、key=TempKey1 のユニークな水準別に、新しい列を作って振り分けられる。


> tidypest.raw.inv
      Season Site Control.After Control.Before Treat.After Treat.Before
1     August    1             1              4           4            6
2     August    2             0              5           6            2
3     August    3             0              5           1            4
4     August    4             0              3           6            1
5     August    5             0              4           5            2
6     August    6             1              8  あらへん。            2
7  September    1             3              2           1            1
8  September    2             3              1           2            5
9  September    3             3              4           0            3
10 September    4             5              1           0            2
11 September    5             7              2           2            0
12 September    6             5              4           1            5

spread() で新しく作られる列の名前は、デフォルトでは key 列の各水準がそのまま使われ、変換前の key の列名であった "TempKey1" は新規列名に盛り込まれない。変換後のデータフレームにおいて列名に統一性を持たせたければ、sep="." などとすると、生成される列名を "TempKey1.Control.After" などとできる。

データ列ラベルを結合する unite と分割する separate / extract

さて、上記の long <-> wide 変換が自在に出来るようになっただけでも随分な進歩なのだが、データラベルが「試験処理+時間」という 2-factor 構造を持つ欠陥は、tidypest.raw.long1 でもまだ解消されていない。なのでまだ真の tidy data とは呼べない。ここで tidypest.raw.long1$TempKey1 は Treat.Before みたく、"Treatment"+"."+"Time" という構造をもつ文字列である。つまり、ピリオドの前までを「処理」、ピリオドよりも後を「時間」として別々のデータ列に切り出すことができれば、所定の目標を達せられそうだ。

データ列ラベルを分割する tidyr::separate 関数


# データ列の分解には tidyr:separate を使うのが楽。
library(tidyverse) # load the package of "tidyverse"
tidypest.long <- tidypest.raw.long1 %>%
    tidyr::separate( col=TempKey1, into=c("Treatment", "Time"), sep='[^[:alnum:]]',
                     remove=TRUE, convert=FALSE, extra='warn', fill='warn' )
tidypest.long

# tidypest.raw からの一連の処理をパイプライン化すると
tidypest.long <- tidypest.raw %>%
    tidyr::gather(   key=TempKey1, value=TempValue,
                     Treat.Before, Treat.After, Control.Before, Control.After,
                     na.rm=FALSE, convert=FALSE, factor_key=FALSE ) %>%
    tidyr::separate( col=TempKey1, into=c("Treatment", "Time"), sep='[^[:alnum:]]',
                     remove=TRUE, convert=FALSE, extra='warn', fill='warn' )
tidypest.long

この段階で、視覚化も統計モデルへの当てはめもドンと来いの tidy data を得る。


> tidypest.long
      Season Site Treatment   Time TempValue
1     August    1     Treat Before         6
2     August    2     Treat Before         2
3     August    3     Treat Before         4
4     August    4     Treat Before         1
5     August    5     Treat Before         2
6     August    6     Treat Before         2
7  September    1     Treat Before         1
8  September    2     Treat Before         5
9  September    3     Treat Before         3
10 September    4     Treat Before         2
(以下省略)

separate( data, col, into, sep = "[^[:alnum:]]+", remove = TRUE,
          convert = FALSE, extra = "warn", fill = "warn", ...)

引数
data    データフレーム。
col    分割対象とするデータ列の名前、もしくはアドレス(何番目の列かを表す数値)。tidyselect::vars_pull() に渡される。引用符で囲む必要はない。
into    新しく作られる変数(複数ある)の名前を文字列ベクトルで指定。

sep    データ列の分割場所となる文字(セパレータ)。分割規則を文字列で与えるか、何番目の文字までを切り取るかを整数ベクトルとして指定する。
    文字列で与えた場合は正規表現として解釈される。デフォルトの "[^[:alnum:]]+" はアルファベットと半角数字以外、全ての文字で列を分割しようとする。
    数値(須藤注:整数ベクトル)で与えた場合は、正数であれば左から数えて(負数であれば右から)n 字までを切り取り、n+1字目から次の列が始まるよう分割される。sep 引数のベクトル要素は into の要素数より1つだけ少なく与えられるべきものである。

オプションの引数
remove    デフォルトは TRUE で、分割対象になった元のデータ列は関数の出力ファイルから取り除かれる。
convert    デフォルトはFALSEだが、もしも TRUE ならば、変換生成後のデータ列に type.convert() を as.is = TRUE として掛ける。元のデータが integer, numeric or logical であるときに有用。

extra    If sep is a character vector, this controls what happens when there are too many pieces. There are three valid options:
    "warn" (the default): emit a warning and drop extra values.
    "drop": drop any extra values without a warning.
    "merge": only splits at most length(into) times

fill    If sep is a character vector, this controls what happens when there are not enough pieces. There are three valid options:
    "warn" (the default): emit a warning and fill from the right
    "right": fill with missing values on the right
    "left": fill with missing values on the left
...    Additional arguments passed on to methods.

関数 spread と separate はどちらもデータ列を「分ける」処理なので紛らわしいが、得られる結果は全く異なる。spread は key 列が保持するデータを層別し、その水準の数だけ独立した列を作る。作られた列に入るデータ値は、引数 value で指定されたものである。つまり key は字義通り、分類のキーとして扱われている。

separate は対象の col 列が含む各データ要素を文字列として解釈し、その文字列を一定の規則 sep に基づいて分割する。分割された文字列が、引数 into に基づいて新設された、データ列たちの値として割り振られる。

引数 sep に数値を与えた場合の挙動がわかりにくいので実験しておこう。


hoge <- tidypest.raw.long1 %>%
    tidyr::separate( col=TempKey1, into=c("Treatment", "Time", "Hoge", "Fuge"), sep=c(2, 10, 1),
                     remove=TRUE, convert=FALSE, extra='warn', fill='warn' )

> hoge
      Season Site Treatment     Time Hoge          Fuge TempValue
1     August    1        Tr eat.Befo        reat.Before         6
2     August    2        Tr eat.Befo        reat.Before         2
3     August    3        Tr eat.Befo        reat.Before         4
4     August    4        Tr eat.Befo        reat.Before         1
5     August    5        Tr eat.Befo        reat.Before         2
(以下省略)

hoge <- tidypest.raw.long1 %>%
    tidyr::separate( col=TempKey1, into=c("Treatment", "Time", "Hoge", "Fuge"), sep=c(2, -2, -1),
                     remove=TRUE, convert=FALSE, extra='warn', fill='warn' )

> hoge

      Season Site Treatment       Time Hoge Fuge TempValue
1     August    1        Tr   eat.Befo    r    e         6
2     August    2        Tr   eat.Befo    r    e         2
3     August    3        Tr   eat.Befo    r    e         4
4     August    4        Tr   eat.Befo    r    e         1
5     August    5        Tr   eat.Befo    r    e         2
(以下省略)

要するに、sep の i 番目の要素 s_i が正の数であれば、i 番目の要素は常に列ラベルの冒頭からの s_i 字を切り取ろうとする。この際、たとえば sep[1]==2 かつ sep[2]==2 だと、2番目の要素は自分が切り取った2文字をお兄ちゃんに持って行かれてしまい、取り分ゼロである。一方、sep の最後尾の要素は、独立して高い優先度を持つようだ。sep=c(2, 10, 1) とした最初の例では、最後尾の 1 により「1字目を除いた全部」が Fuge に与えられている。哀れ Hoge は何も貰えない。

データ列ラベルを高度な正規分割表現に従って分割する tidyr::extract 関数

データ列ラベルを分割する別の関数が extract である。separate の sep 引数を、regex すなわち正規表現に置き換えたものになる。


# 列ラベルの分割は、以下の関数 tidyr::extract でも可能。
tidypest.long <- tidypest.raw.long1 %>%
    tidyr::extract( col=TempKey1, into=c("Treatment", "Time"),
                    regex="([[:alpha:]]+)\\.([[:alpha:]]+)",
                    remove=TRUE, convert=FALSE )
tidypest.long

# 以下のように書いても良い。
tidypest.long <- tidypest.raw.long1 %>%
    tidyr::extract( col=TempKey1, into=c("Treatment", "Time"),
                    regex="([[:alpha:]]+)[[:punct:]]([[:alpha:]]+)",
                    remove=TRUE, convert=FALSE )
tidypest.long

正規表現は「RjpWiki: R における正規表現」 のページを参照のこと。[[:alpha:]]+ は「アルファベット(小文字および大文字)が1回以上続くパターン」にマッチし、 \\. はピリオド . という文字にマッチする。パターンマッチの中でエスケープさせるため、バックスラッシュを2回打つ必要がある。代替表記の [[:punct:]] は「パンクチュエーション文字 ! " # $ % & ' ( ) * + , - . /」にマッチする。。


extract(data, col, into, regex = "([[:alnum:]]+)", remove = TRUE, convert = FALSE, ...)

引数
data    データフレーム。
col    分割対象とするデータ列の名前もしくは場所(何番目の列か)。tidyselect::vars_pull() に渡される。引用符で囲む必要はない。
into    新しく作られる変数(複数ある)の名前を文字列ベクトルで指定。

regex    望ましい名前ラベルを抽出するための正規表現を1つ指定する。この正規表現に含まれるグループ(正規表現では () で囲むことで定義される)が、引数 into に入れた各要素と一対一対応する必要がある。

オプションの引数
remove    デフォルトは TRUE で、分割対象になった元のデータ列は関数の出力ファイルに含まれない。
convert    デフォルトはFALSE。もしも TRUE ならば、変換生成後のデータ列に type.convert() を as.is = TRUE として掛ける。元のデータが integer, numeric or logical であるときに有用。
...    正規表現を処理するため、regexec() に渡したい追加の引数があれば。

separate と extract はどちらを使っても似たような仕事ができるが、データの分割位置が行ごとにバラバラで、単純な固定長での分割が難しい場合には、extract で書くほうが楽かもしれない。

データ列ラベルを単純結合する tidyr::unite 関数

列ラベルの分割には separate や extract が使えるように、unite 関数でその逆、つまりデータフレームに含まれる複数の列を単純結合できる。


# データ列の結合には tidyr:unite を使う。
library(tidyverse) # load the package of "tidyverse"
tidypest.long.2way <- tidypest.long %>%
    tidyr::unite( col=TempKey2, Treatment, Time, sep="_", remove=TRUE )
tidypest.long.2way

> tidypest.long.2way
      Season Site       TempKey2 TempValue
1     August    1   Treat_Before         6
2     August    2   Treat_Before         2
3     August    3   Treat_Before         4
4     August    4   Treat_Before         1
5     August    5   Treat_Before         2
(以下省略)

unite(data, col, ..., sep = "_", remove = TRUE)

引数
data    データフレーム。
col    結合して作られる新たな列名。文字列ないしシンボルで与える。"" で囲んでも囲まなくてもいい。rlang::quo_name() の規則に従って渡されるが、実際のオブジェクトに対応しないシンボル名での指定は現在の tidyverse では非推奨であり、後方互換性のためだけに残されている。
...    結合対象の列を指定。何も指定しない場合、全データ列を選択。列名は "" で囲んでも囲まなくてもいい。列 x と列 z の間にある全てを選びたければ x:z としてよい。-y とすれば列 y を除外する。さらに詳細は dplyr::select() のドキュメントを参照。
sep    結合したデータの間に入るセパレータ文字列。デフォルトは "_" で、たとえば "Control_Before" などとなる。
remove    デフォルトは TRUE で、結合対象になった元のデータ列は関数の出力ファイルに含まれない。

では、今回はここまで。


# いったん、ここまでのデータを保存する。
today <- "180209"
save( list=ls(), file=paste("tidy", today, "dat", sep=".") ) # ワークスペースをバイナリ形式で

# よみこみ
today <- "180209"
load( file=paste("tidy", today, "dat", sep=".") )

# csvで保存
write_csv( tidypest.long, path=paste("tidypest", "long", "csv", sep="."))