こんにちは、usagi-sanです。
今回は、統計解析に用いるデータを整形する方法について解説します。
統計解析をする際に、約8割から9割の作業がこのデータ整形に費やされます。
マジで面倒くさいです。さらに、この作業を怠ると後の統計解析で、解析結果に誤りが見つかったり、そもそも解析ができなかったりするなど、大抵の人は発狂します。
ここでは、アルバイトでのデータ解析の経験で用いたテクニックのみ紹介します。
今回用いるプログラミングコードは以下からダウンロードできます。
R言語 データ整形練習 Rスクリプト
ぜひぜひダウンロードしてください。
練習用データをダウンロードしよう
今回用いるデータは統計データ分析コンペティションでダウンロードできます。csvファイルをあらかじめダウンロードしておいてください。また、記事下のダウンロードリンクから加工済みのデータも入手できます。
練習用データフレームの加工
ダウンロードしたら作業ディレクトリ(rファイルを保存した場所)で、以下のread.csvを用いて"SSDSE-2020A.csv"を読み込んでみましょう。
1 2 | #データ整形の方法 df <- read.csv("SSDSE-2020A.csv", fileEncoding = "CP932") |
これで、dfに先ほどダウンロードしたcsvファイルがデータフレームとして代入されました。
試しにコンソール上で次のように
1 | df[,1:5] |
と実行するとわかる通り、このdfは欠損値をもっていません。
このままでは、教育用標準データセットなのに、全然練習用としてよろしくないので、
次のようにしてランダムに欠損値を代入していきます。
1 2 3 4 5 6 7 8 | for(i in 1:ncol(df)){ ##データ整形の練習として複数の種類の欠損値を代入 for (j in 1:10){ random <- sample(3:nrow(df), 30) #sample()ベクトルの要素をランダムに返す df[random[1:10], i] <- "" df[random[11:20], i] <- "欠損値" df[random[21:30], i] <- "unknown" } } |
3行目で、関数sampleを用いることで、データフレームの行数の中から、ランダムに30個の数字をとりだし、それらをrandomというベクトルに代入しています。
3:nrow(df)としたのは、3行目以降にデータの値が格納されているためです(1,2行目は列名など)。
4~6行目で3つの種類の欠損値「"","欠損値","unknown"」を先ほどのrandomから、ランダムに選ばれた要素を行番号として、これらの欠損値を代入しています。
これで練習用のデータフレームが完成しました。
関数sample
欠損値を代入する際に用いた関数sampleを補足として説明します。
ベクトルの要素をランダムに抽出したい場合は、関数sampleを用います。
以下に関数sample(x, size, replace = FALSE, prob = NULL)の引数を表にまとめました。
size | ベクトルxから抽出する標本の数 |
replace | TRUEの場合、復元抽出する。 |
prob | ベクトルxの各要素の抽出される確率(デフォルトだと確率はすべて同じ)。 |
加工済みのデータフレームを保存する
次のように、作成したデータフレームはcsvファイルとして保存するのをお勧めします。
1 2 | #NA挿入後のデータフレームを保存(ここでは列数が多いので30列まで扱う) write.csv(df[,1:30], "SSDSE-2020A(NA挿入後).csv", fileEncoding = "CP932", row.names = F) |
fileEncoding="CP932"としているのは、RStudioのエンコーディングがデフォルトでCP932だからです(windowsとmacで同じ.Rファイルを開くときに互換性が高いため使っています)。
今後は、上で保存した"SSDSE-2020A(NA挿入後).csv"というファイルの整形方法を紹介します。
統計解析を行いやすいデータに加工していきます。
データ整形
列名の整理
先ほど保存したファイルをread.csvを使って読み込んでみましょう。
特に意味はないと思われますが、新しく読み込むことで、欠損値が挿入された数値データの列(総人口など)が文字列データ(""で囲まれているデータ)になっていることがわかります。解析するデータはこのように、数値が文字列となっていることが多いため、改めてread.csvを用いました。
1 2 | #準備が整ったので、再度データを読み込む df <- read.csv("SSDSE-2020A(NA挿入後).csv", fileEncoding = "CP932") |
最初にデータの列名を確認しましょう。データフレームの列数やどのようなデータから構成されているか視覚的にとらえることが可能です。
1 2 3 4 5 6 7 8 9 10 11 12 | > #データの構成の確認のために、列名を参照する > colnames(df) #変な列名を返します [1] "code" "prefecture" "municipality" [4] "A1101" "A110101" "A110102" [7] "A1102" "A110201" "A110202" [10] "A1301" "A130101" "A130102" [13] "A1302" "A130201" "A130202" [16] "A1303" "A130301" "A130302" [19] "A1419" "A141901" "A141902" [22] "A1700" "A4101" "A4200" [25] "A5101" "A5102" "A7101" [28] "A710101" "A710201" "A810102" |
列名をコンソール上で確かめると、上のように変な列名を返します。
次にデータの一部を表示してみましょう。次のようにデータフレームの一部をコンソール上で表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 | > #1,2列目を確認して、どんなデータになっているか見てみる > df[1:4,1:5] #列名にcode、1行目にcodeに対応する年度が入っています code prefecture municipality A1101 1 year 年度 年度 2015 2 地域コード 都道府県 市区町村 総人口 3 R01100 札幌市 1952356 4 R01202 unknown 函館市 265979 A110101 1 2015 2 総人口(男) 3 910614 4 120376 |
1から4行目をひょうじすることによって 、1行目に「year, 年度、年度、2015、2015」、2行目に「地域コード、都道府県、市区町村、総人口、総人口(男)」というデータのヘッダーを持っていることが分かります。
このように1行目以降にもヘッダー(列名)を持つデータは、統計解析には向いていません。
次に1,2行目の列名を結合し、新しい列名を作っていきます。
関数paste、paste0
文字列を結合させたいときは関数paste、またはpaste0を用います。
関数paste(x, y sep = " ", collase = NULL)とpaste(x, y sep = "", collase = NULL)の引数を以下にまとめます。
sep | 1文字の区切り文字(char型)。pasteの場合、デフォルトの区切り文字は" "でありpaste0の場合、""である。 |
collapse | 2文字以上の区切り文字(string型)。 |
文字列を結合
1,2行目の文字列をpast0を用いて、列ごとに結合させ、これをdfの列名に代入しましょう。
1 2 3 | #データの列名としては、1,2行目のみ必要そうなので、2行を組み合わせて列名とする colnames(df) <- paste0(df[1,], "_" ,df[2,]) #念のため、区切り文字として"_"を用いる df <- df[-c(1,2),] |
上のコードの3行目ではdfの1,2行目を消去しています。
df[-x, ]はベクトルxの行を除くdfを返します。同様にdf[,-x]でxの列を除くデータフレームを返します。
dfをコンソール上で確認してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | > #1~10行,1~5列のデータを見てみる > df[1:10,1:5] #列名が簡潔に整理されていることが確認できます year_地域コード 年度_都道府県 年度_市区町村 3 R01100 札幌市 4 R01202 unknown 函館市 5 R01203 北海道 小樽市 6 R01204 北海道 旭川市 7 北海道 室蘭市 8 欠損値 欠損値 釧路市 9 R01207 北海道 10 R01208 北海道 北見市 11 R01209 北海道 夕張市 12 R01210 岩見沢市 2015_総人口 2015_総人口(男) 3 1952356 910614 4 265979 120376 5 121924 unknown 6 339605 156402 7 88564 8 174742 82185 9 169327 80994 10 unknown unknown 11 8843 4092 12 unknown 39319 |
dfの列名が"year_地域コード"のように区切り文字"_"によって区切られた列名に変更されてるのが確認できます。
年度別の人口のデータの列名を1行で表現できました。
注意として、今回2行を結合させて新しい列名としましたが、これは使用するデータによって変わるため、そのデータに適した列名に変更してください。大事なのは列名は複数行にまたがって定義せず、colnames(df)だけがdfの列名を意味することです。
欠損値の整理
次に、最初に代入した欠損値の整理をしていきます。
今、「""、"欠損値"、"unknown"」という3つの欠損値があるため、これらをNAに変更します。
このようなNAではない欠損値がデータに含まれていると、統計解析に支障が出てきます。
予め欠損値に相当するデータをNAに変更しておきましょう。
データの置き換えには関数replaceを用います。
次のようにfor文、またはapplyを用いて、3つの欠損値をNAに置き換えます。
1 2 3 4 5 6 7 8 9 10 11 | #欠損値を整理する #for文を用いた例 for(i in 1:ncol(df)){ df[,i] <- replace(df[,i], df[,i] == "" | df[,i] == "欠損値" | df[,i] == "unknown", NA) } #apply関数を用いた例 #df<- apply(df, 2, function(x){ # return(replace(x, x == "" | x == "欠損値" | x == "unknown" , NA)) # } #) |
上のfor文または、applyのどちらか一方を実行してください。
実際に欠損値がNAに変更されているか確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | > df[1:10,1:5] year_地域コード 年度_都道府県 年度_市区町村 3 R01100 <NA> 札幌市 4 R01202 <NA> 函館市 5 R01203 北海道 小樽市 6 R01204 北海道 旭川市 7 <NA> 北海道 室蘭市 8 <NA> <NA> 釧路市 9 R01207 北海道 <NA> 10 R01208 北海道 北見市 11 R01209 北海道 夕張市 12 R01210 <NA> 岩見沢市 2015_総人口 2015_総人口(男) 3 1952356 910614 4 265979 120376 5 121924 <NA> 6 339605 156402 7 88564 <NA> 8 174742 82185 9 169327 80994 10 <NA> <NA> 11 8843 4092 12 <NA> 39319 |
<NA>に置き換わっていることが確認できました(ブログ上では<NA>が表示されません)。
これで欠損値の整理は完了です。
数値データの整理
次に、身長、体重などの数値データの整理をしていきます。
R言語では数値データをnumericと呼びます。
まず、どの列名を参照し、どの列が数値データであるかを確認します。
1 2 3 4 5 6 7 8 9 10 11 12 | > #数値データの整理 > colnames(df)[4:ncol(df)] #4列名以降が人口や人数を表すので、4列目以降をnumaric型にする [1] "2015_総人口" [2] "2015_総人口(男)" [3] "2015_総人口(女)" [4] "2015_日本人人口" [5] "2015_日本人人口(男)" [6] "2015_日本人人口(女)" [7] "2015_15歳未満人口" [8] "2015_15歳未満人口(男)" [9] "2015_15歳未満人口(女)" [10] "2015_15~64歳人口" |
上のように、dfの4列目以降が総人口や人口の数値データで構成されていることが分かります。
試しに、is.numericを用いて4列目以降が数値データであるかを確認してみます。
1 2 3 4 5 6 7 | > apply(df[,4:ncol(df)], 2, is.numeric) 2015_総人口 2015_総人口(男) 2015_総人口(女) FALSE FALSE FALSE 2015_日本人人口 2015_日本人人口(男) 2015_日本人人口(女) FALSE FALSE FALSE 2015_15歳未満人口 2015_15歳未満人口(男) 2015_15歳未満人口(女) FALSE FALSE FALSE |
すべてFALSEとなっていて、数値データになっていないことが分かります。
df[1:10, 4]を参照すると、次のように数値データが文字列になっていることがわかります。
1 2 | > df[1:10,4] [1] "1952356" "265979" "121924" "339605" "88564" "174742" "169327" NA "8843" NA |
R言語では、数値データの中に「""、"欠損値"、"unknown"」のような文字列が1つでも入っていると、その列は文字列のデータとして扱われます。
本来、この文字列のデータは数値として扱いたいため、次のようにas.numericを用いて文字列を数値に変更する必要があります。
1 2 3 4 5 6 7 8 9 10 | #for文を用いた例 for(i in 4:ncol(df)){ df[,i] <- as.numeric(df[,i]) } #apply関数を用いた例 #df[,4:ncol(df)]<- apply(df[,4:ncol(df)], 2, function(x){ # return(as.numeric(x)) #} #) |
上のfor文、またはapplyを実行した後に、同じ列を参照してみます。
1 2 3 | > df[1:10,4] [1] 1952356 265979 121924 339605 88564 [6] 174742 169327 NA 8843 NA |
ダブルクオーテーション""が取り除かれ、数値データに置き換わりました。
これでdfの4列目以降の全ての数値データの修正が完了いたしました。
因子データの整理
最後に因子データを整理していきます。
R言語では因子をfactorと呼びます。
数値データと同様に、どの列が因子であるかをみていきます。
1 2 3 4 | > #因子データの整理 > colnames(df)[1:3] #2,3列目のデータは都道府県、市区町村であるため、factor型にする [1] "year_地域コード" "年度_都道府県" [3] "年度_市区町村" |
dfの1から3列目をみてみると、2,3列目が都道府県と市区町村であり、これらは因子データであることが分かりました(因子とは、「男性、女性」のような水準を持つデータです)。
is.factorを用いて、これらが因子であるかを確認しましょう。
1 2 3 | > apply(df[,c(2,3)], 2, is.factor) 年度_都道府県 年度_市区町村 FALSE FALSE |
Flaseを返し、因子ではないことが分かります。
試しに、2列目のデータの一部を見てみます。
1 2 | > df[1:10,2] [1] NA NA "北海道" "北海道" "北海道" NA "北海道" "北海道" "北海道" NA |
上の実行結果のように、文字列となっています(factorである場合、上の実行結果にlevelsが表示されます)。
as.factorを用いて2,3列目のデータを因子(factor)に変更します。
1 2 3 4 5 6 7 8 9 10 | #for文を用いた例 for(i in 1:3){ df[,i] <- as.factor(df[,i]) } #apply関数を用いた例 #df[,1:3]<- apply(df[,1:3], 2, function(x){ # return(as.factor(x)) #} #) |
2列目のデータの一部を見てみます。
1 2 3 | > df[1:10,2] [1] <NA> <NA> 北海道 北海道 北海道 <NA> 北海道 北海道 北海道 <NA> 47 Levels: 愛知県 愛媛県 茨城県 岡山県 沖縄県 岩手県 岐阜県 宮崎県 宮城県 京都府 熊本県 群馬県 ... 和歌山県 |
上のように47個の都道府県をLevelsにもつfactorに変更されました。
これで因子データの整理は完了しました。
関数replace
補足として、データを置き換える際に用いた関数replaceの説明をします。
関数replace(x, list, values)の引数を以下の表にまとめました。
list | 置き換えるインデックス。真偽値のベクトルのTRUEの部分が置き換える要素となる。 |
values | 置き換え後の値。 |
まとめ
データフレーム整形方法を一通り解析しました
dfをコンソール上で確認すると、より解析に適したデータになったことが分かります。
次の記事では、今回整形したデータをより細かく調整していきます。
数値データをカテゴリカルデータに変更したり、因子の水準を変更したりしていきます。