📌
  • package

  • ROpenSciの実験的パッケージリポジトリropenscilabsで、便利そうなパッケージを見つけた。{tabulizer}というものだ。このパッケージはTabulaというオープンソースツールの機能を利用して、PDF中に含まれる表から、値を取り出すというもの。

    政府や企業の報告書はPDFであることが多く(二次利用を想定していないのだろうが)、表としてデータが収められていることがしばしばある。PDFからのデータ取得方法として、Rでは {tm}使う方法{pdftools}利用する方法がそれぞれあるが、{pdftools}ではテキストベースでの抽出となるため、表データの抽出からRの特徴とも言えるデータフレーム形式への変換が困難であった。そこでこの{tabulizer}を使うことで、PDFに含まれる表データの抽出が可能となり、Rで扱えるデータの幅が広がることが考えられる。

    使ってみた感じ、PDFからのデータ取得の決定版となりそうな大変便利なパッケージであることがわかったので、その機能を試しておく。

    パッケージは現在CRANには登録されていないのでGitHubのリポジトリからダウンロードしてくる。利用の際に{tabulizerjars}というパッケージも必要になるのでこちらもインストール({rJava}に依存しているのでWindows環境では面倒かも)。なおGitHubリポジトリ上のインストールの際は{githubinstall}を利用するのが楽。

    library(githubinstall)
    githubinstall(c("tabulizerjars", "tabulizer"))

    使い方

    GitHubのvignettesに解説記事があるが一通り紹介しておく。

    library(tabulizer)

    PDFからの表データ取得: extract_tables

    メインとなる関数がextract_tables()。この関数では引数に与えたPDFファイル(ローカル上でもウェブ上でも良い)に含まれる表からデータを抽出する。引数がいくつか用意されていて、PDFへのパス以外何も指定しない場合にはすべてのPDFのページを対象に表データの抽出を実行する。返り値は行列のリストが初期値となっている。

    例として、{tabulizer}パッケージインストール時に付属するPDFファイルを対象に操作してみる。なおこのファイルは https://github.com/leeper/tabulizer/raw/master/inst/examples/data.pdf にあるものと同じなので、実行結果を比較してみたい方はリンク先のPDFを見てもらうと良いだろう。

    path2pdf <- system.file("examples", "data.pdf", package = "tabulizer")
    out <- extract_tables(path2pdf)
    class(out) # リストクラス
    ## [1] "list"
    # 表データの取り出し
    out[[1]] %>% head()
    ##      [,1]   [,2]  [,3]    [,4]  [,5]   [,6]    [,7]    [,8] [,9] [,10] 
    ## [1,] "mpg"  "cyl" "disp"  "hp"  "drat" "wt"    "qsec"  "vs" "am" "gear"
    ## [2,] "21.0" "6"   "160.0" "110" "3.90" "2.620" "16.46" "0"  "1"  "4"   
    ## [3,] "21.0" "6"   "160.0" "110" "3.90" "2.875" "17.02" "0"  "1"  "4"   
    ## [4,] "22.8" "4"   "108.0" "93"  "3.85" "2.320" "18.61" "1"  "1"  "4"   
    ## [5,] "21.4" "6"   "258.0" "110" "3.08" "3.215" "19.44" "1"  "0"  "3"   
    ## [6,] "18.7" "8"   "360.0" "175" "3.15" "3.440" "17.02" "0"  "0"  "3"

    Rで利用する場合はデータフレームであると何かと都合が良いので、データフレームとして取り出すオプションとして、引数methodが用意されている。*method*="data.frame"でリストの中身がデータフレームとして与えられるようになる。

    out <- extract_tables(path2pdf, 
                   pages = 2,
                   method = "data.frame")
    
    out
    ## [[1]]
    ##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
    ## 1          5.1         3.5          1.4         0.2  setosa
    ## 2          4.9         3.0          1.4         0.2  setosa
    ## 3          4.7         3.2          1.3         0.2  setosa
    ## 4          4.6         3.1          1.5         0.2  setosa
    ## 5          5.0         3.6          1.4         0.2  setosa
    ## 6          5.4         3.9          1.7         0.4  setosa
    ## 
    ## [[2]]
    ##     X Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
    ## 1 145          6.7         3.3          5.7         2.5 virginica
    ## 2 146          6.7         3.0          5.2         2.3 virginica
    ## 3 147          6.3         2.5          5.0         1.9 virginica
    ## 4 148          6.5         3.0          5.2         2.0 virginica
    ## 5 149          6.2         3.4          5.4         2.3 virginica
    ## 6 150          5.9         3.0          5.1         1.8 virginica

    今回はpages引数で対象ページを指定した。ページ内に複数の表が存在する場合にはデータフレームも2つになる。

    引数methodは既定値でmatrixだが、data.frameやcharacterも選べるようになっている。またさらなるオプションとして、取得結果をRのオブジェクトではなくcsvやtsv、json形式で保存するようにも設定できる。保存の際は作業ディレクトリではなくPDFが位置するディレクトリに作成される(ウェブ上のPDFを対象にすることはできない)。

    extract_tables(path2pdf, 
                   pages = 3,
                   method = "csv")
    # [1] "/Library/Frameworks/R.framework/Versions/3.3/Resources/library/tabulizer/examples"

    表の部分的な抽出もarea引数の指定により可能となっている。これは1つのページから複数のテーブルデータを得る場合などに役立つ。arealist()でPDFの座標位置(上左下右の順番)を与える必要があるので闇雲に対象のデータがある座標を探すことになってしまうが、その際は次のlocate_areas()を利用すると良いだろう。

    extract_tables(path2pdf, 
                   pages = 2,
                   area = list(c(126, 149, 212, 462)),
                   method = "data.frame")
    ## [[1]]
    ##     X Sepal.Width Petal.Length Petal.Width Species
    ## 1 5.1         3.5          1.4         0.2  setosa
    ## 2 4.9         3.0          1.4         0.2  setosa
    ## 3 4.7         3.2          1.3         0.2  setosa
    ## 4 4.6         3.1          1.5         0.2  setosa
    ## 5 5.0         3.6          1.4         0.2  setosa
    ## 6 3.9         1.7          0.4      setosa

    インタラクティブな操作で取得範囲を指定

    Shiny Widgetsの機能を利用したlocate_areas()またはextract_areas()の実行により、インタクティブに取得範囲を指定できるようになる。これは対象のPDFをブラウザ上に描画させ、データ取得を実行する範囲をユーザーで指定する(ドラックで範囲指定)というもの。

    locate_areas(path2pdf, pages = 2, widget = "shiny")
    # [[1]]
    #      top     left   bottom    right 
    # 123.6069 159.5029 208.3006 287.6879 

    返り値の座標をextract_tables()area引数の値として与えてみると

    extract_tables(path2pdf,
                   pages = 2,
                   method = "data.frame",
                   area = list(c(120, 150, 210, 280)))
    ## [[1]]
    ##   Sepal.Length Sepal.Width
    ## 1          5.1         3.5
    ## 2          4.9         3.0
    ## 3          4.7         3.2
    ## 4          4.6         3.1
    ## 5          5.0         3.6

    見事に該当箇所のデータが取得できた!

    もう一つのextract_areas()関数の方は、指定箇所のデータを直接取得するというもの。

    extract_areas(path2pdf, pages = 2)
    # [[1]]
    #      [,1]          
    # [1,] "Petal.Length"
    # [2,] "1.4"         
    # [3,] "1.4"         
    # [4,] "1.3"         
    # [5,] "1.5"         
    # [6,] "1.4"         
    # [7,] "1.7" 

    メタ情報の取得: extract_metadata

    関数extract_metadata()は対象PDFのメタ情報(タイトルやページ数、作成日など)を取得する関数となっている。PDFの概要を知りたい場合に便利だろう(pdftools::pdf_info()と同様の機能)。

    # extract_metadata関数によって取得されるメタデータ
    extract_metadata(path2pdf) %>% names()
    ##  [1] "pages"    "title"    "author"   "subject"  "keywords" "creator" 
    ##  [7] "producer" "created"  "modified" "trapped"

    表サイズの確認

    これはextract_tables()での引数areaの指定に役立つと思われる。

    get_page_dims(path2pdf)
    ## [[1]]
    ## [1] 612 792
    ## 
    ## [[2]]
    ## [1] 612 792
    ## 
    ## [[3]]
    ## [1] 612 792
    get_n_pages(path2pdf) # ページ数
    ## [1] 3

    PDFの分割と結合: split_pdf / merge_pdfs

    PDFを分割したり、複数のPDFを結合する関数が用意されている。

    # 一時フォルダに各ページのPDFが保存される
    (sf <- split_pdf(path2pdf))
    # [1] "/var/folders/8f/s_lbgwks6q7g3lz52q93ngph0000gn/T//Rtmpx4BCAz/data1.pdf" "/var/folders/8f/s_lbgwks6q7g3lz52q93ngph0000gn/T//Rtmpx4BCAz/data2.pdf"
    # [3] "/var/folders/8f/s_lbgwks6q7g3lz52q93ngph0000gn/T//Rtmpx4BCAz/data3.pdf"
    
    # PDFを結合
    merge_pdfs(sf, "merge.pdf")

    PDFのサムネイル生成

    pdftools::pdf_render_page()と同じように、PDFの各ページについてサムネイル画像を生成する関数が用意されている。make_thumbnails()にPDFのパスを与えると、PDFがあるフォルダにpng形式の画像ファイルが生成される(出力先や画像ファイルの拡張子は引数の指定により変更可能。)

    make_thumbnails(path2pdf,
                    pages = 1,
                    format = "png",
                    resolution = 72L,
                    outdir = "inst/")

    © Shinya Uryu, 2016.

    The contents on this site are licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.