名古屋市のデータのCSV化

はじめに

名古屋市:子宮頸がん予防接種調査の結果を報告します(暮らしの情報)でデータがPDFで公開された。「ご意見」フォームで「ExcelかCSVで」とお願いしたが,別の方が問い合わせされて名古屋市からは「改竄を防ぐためpdfのみ」という回答があったという。あかん。これはPDFからデータを抽出するしかない。

匿名の方からPDFを変換したCSVファイルをいただいた。ただ,「項目番号 220, 223, 229 は読み取ることができないファイルがいくつかあったため,全部欠損値にしました」とのことである[以下の私のデータと若干異なっている。1箇所だけ元データと照合したが私のほうが正しいみたいだった]。

私もその後少しだけやってみた。以下はそのメモである。

このデータを使った解析法の例は名古屋市のHPVVデータの解析として別ページにまとめた。

方法

データは上記ページに「調査回答データ(00001から06000)」「調査回答データ(06001から12000)」「調査回答データ(12001から18000)」「調査回答データ(18001から24000)」「調査回答データ(24001から30793)」の5つに分けてPDFで公開されている。ファイル名は kaitodeta1.pdf から kaitodeta5.pdf までである。例えば kaitodeta1.pdf は2429ページあるが,1-155ページが一番左の部分,156-311ページがその右,…と続く。

各列の詳しい説明は detanyuuryokuhoushiki.pdf というファイルに納められている。

一つの方法は,まずこれらを1-155ページ,156-311ページと分割し,それぞれに対してAcrobatでExcel形式に変換し,Excelで編集して一つにまとめることである。もう一つの方法はpdftotextを使う方法である:

for x in kaitodeta*.pdf; do
  pdftotext -raw $x
done

これで kaitodeta1.txt から kaitodeta5.txt までができる。これを次のすごくいいかげんなRスクリプトで処理する(Rでのスクリプト処理参照):

#! /usr/bin/env Rscript

args = commandArgs(trailingOnly=TRUE)
if (length(args) != 1) {
    cat("./do.R filename\n")
    quit()
}
# basename = sub("\\.[^.]*$", "", args[1])
f = file(args[1], "r")
line = gsub("\x0c", "", readLines(con=f, n=1))
cols = 0
for (num in 1:99) {
    outfile = paste0("data", formatC(num, width=2, flag="0"), ".csv")
    while (length(line) != 0) {
        a = strsplit(line, " ")[[1]]
        len = length(a)
        if (len > 0 && all(a == seq(from=cols+1, by=1, length.out=len))) break
        line = gsub("\x0c", "", readLines(con=f, n=1))
    }
    cols = cols + len
    while (length(line) != 0) {
        a = strsplit(line, " ")[[1]]
        len = length(a)
        if (len > 0 && all(a == seq(from=cols+1, by=1, length.out=len))) break
        cat(a, sep=",", file=outfile, append=TRUE)
        cat("\n", file=outfile, append=TRUE)
        line = gsub("\x0c", "", readLines(con=f, n=1))
    }
    cat(outfile, cols, "\n")
    if (length(line) == 0) break
}

上のファイルを do.R という名前で作り,実行許可を与え,

./do.R kaitodeta1.txt

とすると,data??.csv というファイルがたくさんできる(?? は 01 から始まる連番)。これを一つずつ目で見て頭のゴミを取り除いてから,次のスクリプトで行数・列数をチェックする:

#! /usr/bin/env Rscript

args = commandArgs(trailingOnly=TRUE)
for (arg in args) {
    f = file(arg, "r")
    maxlen = 0
    minlen = 999999
    lineno = 0
    while (TRUE) {
        line = readLines(con=f, n=1)
        if (length(line) == 0) break
        a = strsplit(line, ",")[[1]]
        len = length(a)
        if (len > maxlen) maxlen = len
        if (len < minlen) minlen = len
        lineno = lineno + 1
    }
    cat(arg, lineno, minlen, maxlen, "\n")
}

列数がおかしいものは,次のスクリプトで修正する。コマンドライン引数として,CSVファイル名と,空の場合はNAを埋める列番号を与える。

#! /usr/bin/env Rscript

args = commandArgs(trailingOnly=TRUE)
nargs = length(args)
if (nargs < 2) {
    cat("./fix.R filename.csv pos1 [pos2 ...]\n")
    quit()
}
f = file(args[1], "r")
lines = readLines(con=f)
nlines = length(lines)
for (i in 1:nlines) {
    a = strsplit(lines[i], ",")[[1]]
    len = length(a)
    for (j in 2:nargs) {
        pos = as.numeric(args[j])
        if (pos > len || grepl("^[\x21-\x7e]+$", a[pos])) {
            a = append(a, NA, after=pos-1)
            len = len + 1
        }
    }
    lines[i] = paste(a, collapse=",")
}
close(f)
f = file(args[1], "w")
writeLines(lines, con=f, sep="\n")

できた data??.csv を次のスクリプトで横方向にアペンドする(もうちょっとRらしくできるはずであるが手抜きした):

#! /usr/bin/env Rscript

args = commandArgs(trailingOnly=TRUE)
nargs = length(args)
if (nargs < 1) {
    cat("./append.R file1 [file2 ...] >file\n")
    quit()
}
block = vector("list", nargs)
for (i in 1:nargs) {
    f = file(args[i], "r")
    block[[i]] = readLines(con=f)
    len = length(block[[i]])
    close(f)
}
for (j in 1:len) {
    for (i in 1:nargs) {
        cat(block[[i]][j])
        if (i < nargs) cat(",")
    }
    cat("\n")
}

上のスクリプト append.R を使って

./append.R data??.csv >kaito1.csv

のようにして横方向にまとめる。こうしてできた kaito1.csv 〜 kaito5.csv を今度は縦方向にまとめる。こちらはOSのコマンド cat でできる:

cat kaito?.csv >kaito.csv

最後の列275は無理なので諦めた。また,列229は,2〜4番目のファイル(006001-024000)では同じページに必須列がないのでpdftotextでは切り分け不能で,この部分だけNA(欠損値)にした。

最終的なCSVファイルはここに置いておく。間違いがないとはいえないので,ご寄付いただいた別バージョンのものでも解析して同じ結果が出ることを確認されることをお勧めする。

確認

できたCSVを読み出す:

kaito = read.csv("kaito.csv", header=FALSE, colClasses="character", fileEncoding="UTF-8")
dim(kaito)
[1] 30793   274
head(kaito)
      V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12  V13 V14 V15 V16 V17 V18 V19  V20
1 000001  0  0  1  0  0  0  1  0   0   0   0 0000   0   0   0   0   0   2 0000
2 000002  0  1  0  0  0  0  0  0   1   0   1 0000   0   0   0   0   0   1 0000
3 000003  0  1  0  0  1  0  0  0   0   0   2 0000   2   0   1   0   0   1 0000
4 000004  0  0  1  0  0  0  0  0   0   1   1 0000   0   0   0   0   0   1 0000
5 000005  0  0  1  0  0  0  0  1   0   0   2 2610   1   1   0   0   0   2 2610
6 000006  1  0  0  0  1  0  0  0   0   0   1 0000   0   0   0   0   0   1 0000
……

いただいたCSVファイルのほうも読み出してみる:

kaito2 = read.csv("all.csv", colClasses="character")
dim(kaito2)
[1] 30793   274
head(kaito2)
  X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12  X13 X14 X15 X16 X17 X18 X19  X20 X21
1  1  0  0  1  0  0  0  1  0   0   0   0    0   0   0   0   0   0   2    0   0
2  2  0  1  0  0  0  0  0  0   1   0   1    0   0   0   0   0   0   1    0   0
3  3  0  1  0  0  1  0  0  0   0   0   2    0   2   0   1   0   0   1    0   0
4  4  0  0  1  0  0  0  0  0   0   1   1    0   0   0   0   0   0   1    0   0
5  5  0  0  1  0  0  0  0  1   0   0   2 2610   1   1   0   0   0   2 2610   1
6  6  1  0  0  0  1  0  0  0   0   0   1    0   0   0   0   0   0   1    0   0
……

いただいたファイルのほうはleading zerosはsuppressされている(これは問題ない)。

二つのファイルを数値として比較してみる。比較には Comparing vectors or factors with NA にある compareNA() を使った。

compareNA = function(v1, v2) {
    same = (v1 == v2) | (is.na(v1) & is.na(v2))
    same[is.na(same)] = FALSE
    return(same)
}
for (j in 1:274) {
    d = !compareNA(as.numeric(kaito[,j]),as.numeric(kaito2[,j]))
    s = sum(d)
    if (s != 0) {
        cat("●変数", j, "に", s, "個の相違あり\n")
        cat(kaito[d,j], "\n")
        cat(kaito2[d,j], "\n")
    }
}

結果は次の通り:

●変数 180 に 1 個の相違あり
NA 
1 
●変数 181 に 1 個の相違あり
1 
0 
●変数 187 に 1 個の相違あり
0 
不正出血 
●変数 188 に 106 個の相違あり
NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 不正出血 NA NA NA NA NA NA NA NA NA NA NA 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 2 1 0 0 0 0 0 0 0 0 0 0 
●変数 189 に 106 個の相違あり
0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 2 1 0 0 0 0 0 0 0 0 0 0 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
●変数 221 に 1 個の相違あり
1 
0 
●変数 222 に 1 個の相違あり
00 
進路変更のため