Rでのデータインポートまとめ(csv, Excel, SPSS, Stata, html, pdf)


はじめに

データ解析の前に必ず必要になる,データのインポート。
Rで様々な形式のデータをインポートする方法をまとめたメモです。
今回はcsv,Excel,SPSS,Stata,html,pdfについてまとめます。
後ろのやつほど面倒です。

csv

まずはすべての基本となるcsvファイルから。
世の中のファイルがすべてcsvになれば苦労しないんですが…
コロナウイルスが話題なので,
データは東京都_新型コロナウイルス陽性患者発表詳細にあるcsvファイルを使用します。

csvファイルのインポートにはbase::read.csvが元から入っていますが,
やはりreadr::read_csvを使うのがいいでしょう。

# readrはtidyverseに含まれる
library(tidyverse)
read_csv(file = 'https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv', 
         locale = locale(encoding = 'UTF-8'))
# A tibble: 1,116 x 16
      No 全国地方公共団体コード~ 都道府県名 市区町村名 公表_年月日 曜日  発症_年月日 患者_居住地
   <dbl>            <dbl> <chr>      <lgl>      <date>      <chr> <lgl>       <chr>      
 1     1           130001 東京都     NA         2020-01-24      NA          湖北省武漢市~
 2     2           130001 東京都     NA         2020-01-25      NA          湖北省武漢市~
 3     3           130001 東京都     NA         2020-01-30      NA          湖南省長沙市~
 4     4           130001 東京都     NA         2020-02-13      NA          都内       
 5     5           130001 東京都     NA         2020-02-14      NA          都内       
 6     6           130001 東京都     NA         2020-02-14      NA          都内       
 7     7           130001 東京都     NA         2020-02-15      NA          都内       
 8     8           130001 東京都     NA         2020-02-15      NA          都内       
 9     9           130001 東京都     NA         2020-02-15      NA          都内       
10    10           130001 東京都     NA         2020-02-15      NA          都内       
# ... with 1,106 more rows, and 8 more variables: 患者_年代 <chr>, 患者_性別 <chr>,
#   患者_属性 <lgl>, 患者_状態 <lgl>, 患者_症状 <lgl>, 患者_渡航歴の有無フラグ <lgl>,
#   備考 <lgl>, 退院済フラグ <dbl>

これはまあいいでしょう。
それにしてもtidyなデータですね。

Excel

分析用のデータはcsvなどのテキストファイルで保存するのが基本ですが,
世の中にはExcelでデータを提供してるところもあります。Estatとか。
estat-APIという便利なものも整備されてきてはいるので頑張ってもらいたいところです。
(estat-APIについてはこちらの記事がわかりやすい)

ここでは文部科学省学校基本調査の学校数のデータを使います。
Excelファイルのインポートには,
readxlパッケージのread_xlsread_xlsxread_excelが利用できます。
(多分どれでもよい?)

なお先ほどのread_csvと違って,URLから直接取り込むことはできないようなので,
download.fileで一度ダウンロードします。

library(readxl)
download.file(url = 'https://www.e-stat.go.jp/stat-search/file-download?statInfId=000031852301&fileKind=0',
              destfile = 'data/number_of_school.xlsx',
              mode = 'wb')
read_excel(path  = 'data/number_of_school.xlsx')
# A tibble: 79 x 23
   ...1   ...2   ...3   ...4  ...5  ...6  ...7   ...8             ...12 ...13 ...14
   <chr>  <chr>  <chr>  <chr> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr> <chr> <chr>
 1 NA     NA      NA    NA    NA     NA   NA       NA NA    NA    NA    NA    NA    NA   
 2 区分   幼稚園 "幼保連携~ 小学校~ 中学校~ "義務教~ 高等学校~    NA 中等教育~ 盲学校~ 聾学校~ 養護  ~ 特別支援~ 高等専門~
 3 昭和23年~ 1529   "…"    25237 16285 "…"   NA     3575      74    64                 
 4  24   1787   "…"    25638 14200 "…"   NA     4180      74    78    1              
 5  25   2100   "…"    25878 14165 "…"   NA     4292      76    82    3              
 6  26   2455   "…"    26056 13836 "…"   NA     4477      76    84    3              
 7  27   2874   "…"    26377 13748 "…"   NA     4506      77    86    3              
 8  28   3490   "…"    26555 13685 "…"   NA     4572      78    92    5              
 9  29   4471   "…"    26804 13773 "…"   NA     4606      77    96    5              
10  30   5426   "…"    26880 13767 "…"   NA     4607      77    99    5              
# ... with 69 more rows, and 9 more variables: ...15 <chr>, ...16 <chr>, ...17 <chr>,
#   ...18 <dbl>, ...19 <chr>, ...20 <chr>, ...21 <chr>, ...22 <lgl>, ...23 <lgl>

あっ…

rangeを設定して必要な情報に絞ってインポートします。

read_excel(path  = 'data/number_of_school.xlsx', range = 'C4:V76')
# A tibble: 72 x 20
   幼稚園 `幼保連携型\r\n認定こども~ 小学校 中学校 `義務教育\r\n学校`~ 高等学校  ...7 `中等教育  学校`~
    <dbl> <chr>             <dbl>  <dbl> <chr>               <dbl> <dbl> <chr>           
 1   1529 …                 25237  16285 …                      NA  3575 …               
 2   1787 …                 25638  14200 …                      NA  4180 …               
 3   2100 …                 25878  14165 …                      NA  4292 …               
 4   2455 …                 26056  13836 …                      NA  4477 …               
 5   2874 …                 26377  13748 …                      NA  4506 …               
 6   3490 …                 26555  13685 …                      NA  4572 …               
 7   4471 …                 26804  13773 …                      NA  4606 …               
 8   5426 …                 26880  13767 …                      NA  4607 …               
 9   6141 …                 26957  13724 …                      NA  4575 …               
10   6620 …                 26988  13622 …                      NA  4577 …               
# ... with 62 more rows, and 12 more variables: 盲学校 <chr>, 聾学校 <chr>, `養護
#    学校` <chr>, `特別支援  学校` <chr>, `高等専門  学校` <chr>, 短期大学 <dbl>,
#   ...15 <chr>, 大学 <dbl>, ...17 <dbl>, 専修学校 <chr>, `各種   学校` <dbl>,
#   区分 <chr>

うん…だいぶマシ,かな…
いずれにせよ壮絶な前処理が待ってそうです。

SPSS,Stata

どちらも有料の統計ソフトウェアです。個人で持つにはかなり高額。
さて,savファイル(SPSSのデータ)を渡されて,
「これでちょっと分析してよ」と言われたとします。
ふざけるな!という感じですが,Rにはこれらを処理するパッケージがちゃんとあります。
foreignhavenです。私はhavenの方が好きなのでそちらを今回は紹介します。

havenパッケージでは,
SPSS→read_savまたはread_spss
Stata→read_stata
でファイルを読むことができます。

では実際にやってみましょう。
使用データはPenn World Table version 9.1のStataデータです。

library(haven)
read_stata(file = 'http://www.rug.nl/ggdc/docs/pwt91.dta')
# A tibble: 12,376 x 52
   countrycode country currency_unit  year rgdpe rgdpo   pop   emp   avh    hc  ccon
   <chr>       <chr>   <chr>         <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1 ABW         Aruba   Aruban Guild~  1950    NA    NA    NA    NA    NA    NA    NA
 2 ABW         Aruba   Aruban Guild~  1951    NA    NA    NA    NA    NA    NA    NA
 3 ABW         Aruba   Aruban Guild~  1952    NA    NA    NA    NA    NA    NA    NA
 4 ABW         Aruba   Aruban Guild~  1953    NA    NA    NA    NA    NA    NA    NA
 5 ABW         Aruba   Aruban Guild~  1954    NA    NA    NA    NA    NA    NA    NA
 6 ABW         Aruba   Aruban Guild~  1955    NA    NA    NA    NA    NA    NA    NA
 7 ABW         Aruba   Aruban Guild~  1956    NA    NA    NA    NA    NA    NA    NA
 8 ABW         Aruba   Aruban Guild~  1957    NA    NA    NA    NA    NA    NA    NA
 9 ABW         Aruba   Aruban Guild~  1958    NA    NA    NA    NA    NA    NA    NA
10 ABW         Aruba   Aruban Guild~  1959    NA    NA    NA    NA    NA    NA    NA
# ... with 12,366 more rows, and 41 more variables: cda <dbl>, cgdpe <dbl>, cgdpo <dbl>,
#   cn <dbl>, ck <dbl>, ctfp <dbl>, cwtfp <dbl>, rgdpna <dbl>, rconna <dbl>,
#   rdana <dbl>, rnna <dbl>, rkna <dbl>, rtfpna <dbl>, rwtfpna <dbl>, labsh <dbl>,
#   irr <dbl>, delta <dbl>, xr <dbl>, pl_con <dbl>, pl_da <dbl>, pl_gdpo <dbl>,
#   i_cig <dbl+lbl>, i_xm <dbl+lbl>, i_xr <dbl+lbl>, i_outlier <dbl+lbl>,
#   i_irr <dbl+lbl>, cor_exp <dbl>, statcap <dbl>, csh_c <dbl>, csh_i <dbl>,
#   csh_g <dbl>, csh_x <dbl>, csh_m <dbl>, csh_r <dbl>, pl_c <dbl>, pl_i <dbl>,
#   pl_g <dbl>, pl_x <dbl>, pl_m <dbl>, pl_n <dbl>, pl_k <dbl>

無事読めてますね。
これならわざわざSPSSとか買う必要もないかも。

html

これまではデータが単体のファイルとして存在していましたが,
htmlとpdfに関してはデータがどこにあるのかを特定して取得するという作業が必要になります。
いわゆるスクレイピングです。この章ではまずhtmlのスクレイピングを紹介します。

例として日経平均株価(https://kabutan.jp/stock/kabuka?code=0000&ashi=day&page=1)
のデータをスクレイピングします。
Rでのスクレイピングはrvestパッケージを使うのが良いでしょう。
主な流れとしては,
read_htmlでスクレイピングしたいサイトのhtmlを読み込む。
html_nodeで欲しいデータがページ内のどこにあるのか指定する。
html_attrhtml_texthtml_tableなりで欲しいデータを取り出す。
のようになります。

では実際にやってみましょう。

library(rvest)
# 何度もスクレイピングするのはwebサイトにもPCにも負荷がかかるので一度保存しておく
html <- read_html('https://kabutan.jp/stock/kabuka?code=0000&ashi=day&page=1')
html %>% 
  html_node(xpath = '//*[@id="stock_kabuka_table"]/table[2]') %>% 
  html_table()

途中の//*[@id="stock_kabuka_table"]/table[2]はxpathというやつで,
ページ中段の表を指定するパスです。
google chromeならば,
右クリック→検証から開けるデベロッパツールから引っ張ってくるのが楽です。
こちらのページが参考になります。
https://www.octoparse.jp/blog/essential-for-web-scraping-xpath-introduction/

以上のコードを走らせた結果がこちら。

       日付      始値      高値      安値      終値    前日比 前日比    売買高()
1  20/04/06 17,857.99 18,672.26 17,802.62 18,576.30   +756.11      4.2 1,679,620,000
2  20/04/03 17,951.44 18,059.15 17,646.50 17,820.19     +1.47      0.0 1,499,110,000
3  20/04/02 17,934.42 18,132.04 17,707.66 17,818.72   -246.69     -1.4 1,644,260,000
4  20/04/01 18,686.12 18,784.25 17,871.62 18,065.41   -851.60     -4.5 1,661,420,000
5  20/03/31 19,181.90 19,336.19 18,834.16 18,917.01   -167.96     -0.9 1,862,450,000
6  20/03/30 18,884.07 19,084.97 18,578.20 19,084.97   -304.46     -1.6 1,924,440,000
7  20/03/27 19,021.97 19,389.43 18,832.21 19,389.43   +724.83      3.9 2,368,330,000
8  20/03/26 19,234.77 19,240.29 18,512.81 18,664.60   -882.03     -4.5 2,049,670,000
9  20/03/25 18,446.80 19,564.38 18,446.80 19,546.63 +1,454.28      8.0 2,280,280,000
10 20/03/24 17,206.88 18,100.39 17,197.14 18,092.35 +1,204.57      7.1 2,256,860,000
11 20/03/23 16,570.57 17,049.03 16,480.95 16,887.78   +334.95      2.0 2,697,390,000
12 20/03/19 16,995.77 17,160.97 16,358.19 16,552.83   -173.72     -1.0 3,017,690,000
13 20/03/18 17,154.08 17,396.84 16,698.46 16,726.55   -284.98     -1.7 2,718,810,000
14 20/03/17 16,726.95 17,557.04 16,378.94 17,011.53     +9.49      0.1 3,065,170,000
15 20/03/16 17,586.08 17,785.76 16,914.45 17,002.04   -429.01     -2.5 2,345,690,000
16 20/03/13 18,183.47 18,184.46 16,690.60 17,431.05 -1,128.58     -6.1 3,459,380,000
17 20/03/12 19,064.51 19,142.18 18,339.27 18,559.63   -856.43     -4.4 2,583,520,000
18 20/03/11 19,758.26 19,974.83 19,392.25 19,416.06   -451.06     -2.3 1,996,320,000
19 20/03/10 19,474.89 19,970.35 18,891.77 19,867.12   +168.36      0.9 2,570,370,000
20 20/03/09 20,343.31 20,347.19 19,472.26 19,698.76 -1,050.99     -5.1 2,518,470,000
21 20/03/06 21,009.80 21,061.20 20,613.91 20,749.75   -579.37     -2.7 1,749,640,000
22 20/03/05 21,399.87 21,399.87 21,220.76 21,329.12   +229.06      1.1 1,383,840,000
23 20/03/04 20,897.20 21,245.93 20,862.05 21,100.06    +17.33      0.1 1,507,670,000
24 20/03/03 21,651.99 21,719.78 21,082.73 21,082.73   -261.35     -1.2 1,764,900,000
25 20/03/02 20,849.79 21,593.11 20,834.29 21,344.08   +201.12      1.0 2,035,150,000
26 20/02/28 21,518.01 21,528.13 20,916.40 21,142.96   -805.27     -3.7 2,420,870,000
27 20/02/27 22,255.83 22,272.26 21,844.29 21,948.23   -477.96     -2.1 1,811,410,000
28 20/02/26 22,374.14 22,456.55 22,127.42 22,426.19   -179.22     -0.8 1,677,170,000
29 20/02/25 22,949.37 22,950.23 22,335.21 22,605.41   -781.33     -3.3 1,778,020,000

取れてますね。
ここにいくつかの前処理を施すと以下のようになります。

# A tibble: 29 x 8
   日付         始値   高値   安値   終値  前日比 `前日比%` `売買高(株)`
   <date>      <dbl>  <dbl>  <dbl>  <dbl>   <dbl>      <dbl>        <dbl>
 1 2020-04-06 17858. 18672. 17803. 18576.  756.          4.2   1679620000
 2 2020-04-03 17951. 18059. 17646. 17820.    1.47        0     1499110000
 3 2020-04-02 17934. 18132. 17708. 17819. -247.         -1.4   1644260000
 4 2020-04-01 18686. 18784. 17872. 18065. -852.         -4.5   1661420000
 5 2020-03-31 19182. 19336. 18834. 18917. -168.         -0.9   1862450000
 6 2020-03-30 18884. 19085. 18578. 19085. -304.         -1.6   1924440000
 7 2020-03-27 19022. 19389. 18832. 19389.  725.          3.9   2368330000
 8 2020-03-26 19235. 19240. 18513. 18665. -882.         -4.5   2049670000
 9 2020-03-25 18447. 19564. 18447. 19547. 1454.          8     2280280000
10 2020-03-24 17207. 18100. 17197. 18092. 1205.          7.1   2256860000
# ... with 19 more rows

pdf

いよいよラストです。あんまりpdfからデータセットを収集するという機会はないと思いますが一応…
(むしろpdfでしかそのデータがないのは,提供者のリテラシーが相当ひどいと考えられますが…)

pdfからデータを取るパッケージは主に3つあります。
tabulizer, pdftools, tesseractです。

このうちtesseractはocr,
つまり画像pdfを解析してテキストを取り出すことメインに行うパッケージです。
当然エラーが増えますし,
そもそも画像pdfをデータソースにするくらいならば分析しないほうがマシです。
ただフリーのocrソフトとして考えれば,結構優秀な気がします。

次にtabulizerpdftoolsです。
これらはテキストが埋め込まれたpdfからテキストを取り出すことがメインになります。
pdftoolstesseractと連携していて,ocrもできます)
このうちtabulizerはJavaと連携させるためにひと手間要るということで,
今回はpdftoolsを使ってデータを収集していきます。

(ここら辺のパッケージの話は以下のページを参考にしました。)
https://blog.az.sg/posts/reading-pdfs-in-r/

今回分析するpdfは,2020-03-31における都道府県別のコロナウイルス感染者数のデータです。
URL: https://www.mhlw.go.jp/content/10906000/000617888.pdf

テキストの取得方法はいたって簡単です。これだけでok。

library(pdftools)
text <- pdf_text('https://www.mhlw.go.jp/content/10906000/000617888.pdf')
text
[1] "    新型コロナウイルス感染症(国内事例)の状況(PCR検査陽性者数の累積)(単位:人)\r\n         3月31日(火)  対前日比   うち現在は入院等       うち退院        うち死亡\r\n 東京都         527    78   484     92%  37       7%  6       1% <0件>\r\n 大阪府         245    28   173     71%  70      29%  2       1%\r\n                                                               岩手\r\n 北海道         177    0     46     26% 124      70%  7       4%\r\n 愛知県         176    6    110     63%  47      27% 19      11%  鳥取\r\n 千葉県         164    4    144     88%  19      12%  1       1%\r\n                                                               島根\r\n 兵庫県         147    11   118     80%  18      12% 11       7%\r\n神奈川県         120    5     87     73%  27      23%  6       5%\r\n 埼玉県          98    14    80     82%  15      15%  3       3%\r\n 京都府          68    14    53     78%  15      22%  0       0%\r\n 福岡県          43    20    41     95%   2       5%  0       0%\r\n 新潟県          31    0     21     68%  10      32%  0       0%\r\n 大分県          29    1     28     97%   1       3%  0       0%\r\n 茨城県          24    4     24    100%   0       0%  0       0%\r\n 岐阜県          23    2     21     91%   2       9%  0       0%\r\n 群馬県          19    1     18     95%   0       0%  1       5%\r\n 高知県          17    2      5     29%  12      71%  0       0%\r\n和歌山県          17    0      3     18%  13      76%  1       6%\r\n 福井県          16    1     16    100%   0       0%  0       0%\r\n 熊本県          14    3     11     79%   3      21%  0       0%\r\n 栃木県          14    2     12     86%   2      14%  0       0%\r\n 石川県          13    2      8     62%   5      38%  0       0%\r\n 奈良県          11    0      3     27%   8      73%  0       0%\r\n 三重県          11    0      9     82%   2      18%  0       0%\r\n 愛媛県          9     5      7     78%   2      22%  0       0%\r\n 沖縄県          9     0      7     78%   2      22%  0       0%\r\n 青森県          8     1      8    100%   0       0%  0       0%\r\n 長野県          8     0      5     63%   3      38%  0       0%\r\n 宮城県          7     1      6     86%   1      14%  0       0%\r\n 滋賀県          7     1      5     71%   2      29%  0       0%\r\n 秋田県          6     2      4     67%   2      33%  0       0%\r\n 静岡県          6     2      3     50%   3      50%  0       0%\r\n 山梨県          6     1      4     67%   2      33%  0       0%\r\n 山口県          6     0      3     50%   3      50%  0       0%\r\n 広島県          6     0      5     83%   1      17%  0       0%\r\n 福島県          4     2      4    100%   0       0%  0       0%\r\n 岡山県          4     0      4    100%   0       0%  0       0%\r\n 富山県          3     2      3    100%   0       0%  0       0%\r\n 徳島県          3     2      2     67%   1      33%  0       0%\r\n 宮崎県          3     0      2     67%   1      33%  0       0%\r\n 香川県          2     1      2    100%   0       0%  0       0%\r\n 佐賀県          2     1      2    100%   0       0%  0       0%\r\n 長崎県          2     0      1     50%   1      50%  0       0%\r\n 山形県          1     1      1    100%   0       0%  0       0%\r\n鹿児島県          1     0      1    100%   0       0%  0       0%\r\n  総計        2107   220  1594    76%  456     22%  57      3%\r\n"

あれっ,めっちゃ横に長いですね。
実はテキスト取得自体はこれだけなんですが,
分析できるデータにするには膨大な前処理が必要になります。

ちょっとやってみましょう。
まずは\n(改行)部分でベクトルを切ります。ついでにデータフレームにしておきます。

text %>% 
  str_split('\n') %>% 
  as_tibble(.name_repair = 'unique')
# A tibble: 50 x 1
   ...1                                                                                  
   <chr>                                                                                 
 1 "    新型コロナウイルス感染症(国内事例)の状況(PCR検査陽性者数の累積)(単位:人)\r"~
 2 "         3月31日(火)  対前日比   うち現在は入院等       うち退院        うち死亡\r"~
 3 " 東京都         527    78   484     92%  37       7%  6       1% <0件>\r"           
 4 " 大阪府         245    28   173     71%  70      29%  2       1%\r"                  
 5 "                                                               岩手\r"               
 6 " 北海道         177    0     46     26% 124      70%  7       4%\r"                  
 7 " 愛知県         176    6    110     63%  47      27% 19      11%  鳥取\r"            
 8 " 千葉県         164    4    144     88%  19      12%  1       1%\r"                  
 9 "                                                               島根\r"               
10 " 兵庫県         147    11   118     80%  18      12% 11       7%\r"                  
# ... with 40 more rows

これならなんか行けそうな気がしませんか?

text %>% 
  str_split('\n') %>% 
  as_tibble(.name_repair = 'unique') %>% 

  # 続き
  # タイトルが入っている1,2行目を削る
  slice(-c(1, 2)) %>% 
  # 各列の両端の空白を削る
  mutate(...1 = str_trim(...1, side = 'both')) %>% 
  # 文字の間の空白で行を分ける。
  separate(col = ...1, into = as.character(c(1:10)), sep = '\\s+') %>% 
  # 10行目はいらないので削る,また元のpdfを見ながら列名を埋める
  select(都道府県 = 1, PCR陽性者数 = 2, 前日比 = 3, うち現在入院等 = 4,
             '入院(%)' = 5, うち退院 = 6, '退院(%)' = 7, うち死亡 = 8, '死亡(%)' = 9) %>% 
  # 都道府県にマッチする者のみ残す(総計や,データの入っていない県を落とす)
  filter(grepl(x = 都道府県, pattern = '(東京都|北海道|(?:京都|大阪)府|.{2,3}県)')) %>% 
  # 型変換
  mutate_at(vars(PCR陽性者数:'死亡(%)'), parse_number)
# A tibble: 44 x 9
   都道府県 PCR陽性者数 前日比 うち現在入院等 `入院(%)` うち退院 `退院(%)` うち死亡
   <chr>          <dbl>  <dbl>          <dbl>     <dbl>    <dbl>     <dbl>    <dbl>
 1 東京都           527     78            484        92       37         7        6
 2 大阪府           245     28            173        71       70        29        2
 3 北海道           177      0             46        26      124        70        7
 4 愛知県           176      6            110        63       47        27       19
 5 千葉県           164      4            144        88       19        12        1
 6 兵庫県           147     11            118        80       18        12       11
 7 神奈川県         120      5             87        73       27        23        6
 8 埼玉県            98     14             80        82       15        15        3
 9 京都府            68     14             53        78       15        22        0
10 福岡県            43     20             41        95        2         5        0
# ... with 34 more rows, and 1 more variable: `死亡(%)` <dbl>

これなら使えます。
ただpdfからデータフレームを起こすのは労力が半端じゃないので,
あまりデータソースとしては良くないですね。

まとめ

ファイル形式ごとのインポートの仕方を見てきました。
後半の大変さを見るとやはりcsvでデータは用意しておいて欲しいのと,
tidyデータ(こちらを参考に)という概念がもっと広がってほしいなと思いました…