import pandas as pd
import zipfile
import string
import re
import os
def parse(fn, encoding='latin1', df_name='df', abbreviate=True, minlength=10, verbose=False):
"""
fn: *.zip ファイル(入力) unzip されアスキーデータ *.dat ができる
*.dat があれば,それを使う
この関数は,return() でデータフレームを返すので,すぐ分析を開始できる
副次効果として,df.read_csv() で読める *.csv ファイルを書く
変数情報をデータフレーム *-info.csv に書く
さらに,カテゴリーデータ値を数値ではなく文字列に変換したデータファイルとして読む *-templeta.py ファイルを書く
このテンプレートを使って分析プログラムを書けばよい
abbreviate = True のとき,カテゴリー変数のカテゴリー名を minlength の長さで短縮する
"""
def parse2(arg, f, t):
pos = [i for i, s in enumerate(variable) if arg[0] in s][0]
f[pos] = int(arg[1])
t[pos] = int(arg[3])
def is_num(a):
return all(s in '.0123456789' for s in str(a))
base = fn.replace('.zip', '')
if not os.path.exists(base + '.dat'):
with zipfile.ZipFile(fn) as zip_file:
zip_file.extractall()
fn = str.upper(base) + '.sps'
f = open(fn, encoding=encoding)
src = f.read().split('\n')
f.close()
src = [s.strip() for s in src]
# 最大読み取り桁
s = [s for s in src if 'LRECL' in s]
lrecl = int(re.sub('/LRECL=', '', s[0]))
# df に,変数名と変数ラベルを取り出す
variable = []
label = []
i = [i for i, s in enumerate(src) if s.startswith('VARIABLE LABELS')][0] + 1
while src[i] != '.':
s = src[i].split('"')
variable.append(s[0].replace(' ', ''))
label.append(s[1])
i += 1
n_variables = len(variable)
# df に,読み出し桁数の情報を追加する
f = [0] * n_variables
t = [0] * n_variables
i = [i for i, s in enumerate(src) if s.startswith('DATA LIST FILE')][0] + 1
while src[i] != '.':
field = src[i].replace('(A)', '').split()
if len(field) == 4:
parse2(field, f, t)
elif len(field) == 8:
parse2(field[:4], f, t)
parse2(field[4:], f, t)
else:
print('parse error_')
return 999
i += 1
w = [j - i + 1 for i, j in zip(f, t)]
colspecs = [(i - 1, j) for i, j in zip(f, t)]
df = pd.DataFrame({'variable': variable, 'label': label, 'from': f, 'to': t, 'width': w})
# *.sps の変数に関するデータフレームの書き出し
df.to_csv(base + '-info.csv', index=False)
# データフレームとして読み込み
if sum(w) != lrecl:
print(f'widths error. sum of widths = {sum(w)}, lrecl = {lrecl}\n')
return 999
fn5 = base + '.dat'
print(f'read {fn5} ...')
df2 = pd.read_fwf(fn5, colspecs=colspecs, header=None)
df2.columns = variable
n = df2.shape[0]
print(f' {n} cases, {n_variables} variables')
# csv ファイル書き出し
df2.to_csv(base + '.csv', index=False)
###
# もっとも単純にデータをインポートするだけなら,以下は要らない。
i = [i for i, s in enumerate(src) if s.startswith('VALUE LABELS')][0] + 1
f = open(base + '-template.py', mode='w')
f.write('import pandas as pd\n')
f.write('%s = pd.read_csv("%s.csv")\n' % (df_name, base))
old_str = []
new_str = []
variable = src[i]
i += 1
while True:
if len(src[i]) == 0 or src[i][0] == '/':
if verbose:
print(f'decoding ... {variable}')
if abbreviate:
new_str = [str(s) + ':' + t[:minlength+1] for s, t in zip(old_str, new_str)]
x = df2.loc[:, variable].dropna()
category = [int(y) if is_num(y) else y for y in sorted(set(x))]
count = 0
for s in category:
if not s in old_str:
old_str.insert(count, s)
new_str.insert(count, str(s))
count += 1
if verbose and count != 0:
print(f'levels are completed.')
print(new_str)
dic = {}
dic_str = '{'
for key, value in zip(old_str, new_str):
dic.setdefault(key, value)
if is_num(key):
dic_str = dic_str + str(key) + ': "' + value + '", '
else:
dic_str = dic_str + '"' + key + '": "' + value + '", '
df2 = df2.replace({variable: dic})
dic_str = re.sub(', $', '}', dic_str)
f.write("%s = %s.replace({'%s': %s})\n" % (df_name, df_name, variable, dic_str))
if len(src[i]) == 0:
break
old_str = []
new_str = []
variable = src[i].split()[1]
else:
field = src[i].split('"')
if len(field) == 3:
old_str.append(int(field[0].strip()))
new_str.append(field[1])
elif len(field) == 5:
old_str.append(int(field[1]) if is_num(field[1]) else field[1])
new_str.append(field[3])
i += 1
f.close()
return df2
value labels に記述されない値が NA になってしまう件を修正
parse = function(fn, df.name = "df", abbreviate = TRUE, minlength = 10, verbose = FALSE) {
# fn: *.zip ファイル(入力) unzip されアスキーデータ *.dat ができる
# invisible() でデータフレームを返す
# *.csv ファイルを書く
# 変数情報についてのデータフレーム *-info.csv に書く
# R でfactor 変数を定義するための R コードを *-template.R に書く
# abbreviate = TRUE のとき,factor() の labels を minlength の長さで短縮する
parse2 = function(df, arg) {
pos = grep(arg[1], df$variable)
df[pos, "from"] = as.integer(arg[2])
df[pos, "to"] = as.integer(arg[4])
return(df)
}
base = sub("\\..*$", "", fn)
if (grepl('\\.zip$', fn) && ! file.exists(paste0(fn, ".dat"))) {
unzip(fn)
}
fn = paste0(toupper(base), ".sps")
src = readLines(fn, encoding="latin1")
src = trimws(src)
pos = grep("LRECL", src)
# 最大読み取り桁
lrecl = as.integer(unlist(strsplit(src[pos], "="))[2])
# df に,変数名と変数ラベルを取り出す
begin = which(src == "VARIABLE LABELS")
n.variable = which(src[-(1:begin)] == ".") - 1
variable = character(n.variable)
label = character(n.variable)
for (i in 1:n.variable) {
field = unlist(strsplit(src[begin + i], '"'))
variable[i] = trimws(field[1])
label[i] = field[2]
}
n.variables = length(variable)
# df に,読み出し桁数の情報を追加する
df = data.frame(variable, label, from=0, to=0, width=0)
begin = which(grepl("DATA LIST FILE", src))
n.def = which(src[-(1:begin)] == ".") - 1
for (i in 1:n.def[1]) {
s = gsub("\\(A\\)", "", src[begin + i])
field = unlist(strsplit(s, " +"))
pos = which(field %in% df$variable)
if (length(pos) == 1) {
df = parse2(df, field)
} else if (length(pos) == 2) {
df = parse2(df, field[1:4])
df = parse2(df, field[5:8])
} else {
print("parse error.")
return(999)
}
}
df$width = df$to - df$from + 1
# *.sps の変数に関するデータフレームの書き出し
write.csv(df, paste0(base, "-info.csv"), row.names = FALSE)
# データフレームとして読み込み
if (sum(df$width) != lrecl) {
cat("widths error. sum of widths =", sum(df$width), ", lrecl =", lrecl, "\n")
return(999)
}
fn5 = paste0(base, ".dat")
cat(sprintf("read %s...\n", fn5))
df2 = read.fwf(fn5, width = df$width)
colnames(df2) = df$variable
n = nrow(df2)
cat(sprintf(" %d cases, %d variables\n", n, n.variables))
# csv ファイル書き出し
fn6 = paste0(base, ".csv")
write.csv(df2, fn6, row.names = FALSE)
###
# もっとも単純にデータをインポートするだけなら,以下は要らない。
i = which(src == "VALUE LABELS") + 1
fn7 = paste0(base, "-template.R")
write(sprintf("# template file for data frame -- %s.csv", base), fn7)
write(sprintf("%s = read.csv('%s.csv')", df.name, base), fn7)
old.str = new.str = NULL
variable = src[i]
while (TRUE) {
if (nchar(src[i]) == 0 || substr(src[i], 1, 1) == "/") {
if (verbose) {
cat("decoding...", variable, "\n")
}
if (abbreviate) {
new.str = base::abbreviate(new.str, minlength = minlength, named = FALSE)
new.str = paste(old.str, new.str, sep=":")
}
# もともと存在する level なのに,levels で指定漏れになると存在しないことになるのを修正
category = sort(unique(df2[, variable]))
count = 0
for (s in category) {
if (nchar(trimws(s)) != 0 && ! s %in% old.str) {
old.str = append(old.str, s, after=count)
new.str = append(new.str, s, after=count)
count = count + 1
}
}
df2[, variable] = factor(df2[, variable], levels = old.str, labels = new.str)
old.str2 = sapply(old.str, function(s)
if (is.numeric(s)) s else paste0("'", s, "'"))
old.str2 = paste(old.str2, collapse = ", ")
new.str2 = paste(sprintf("\"%s\"", new.str), sep = ", ", collapse = ", ")
cat(sprintf('%s$%s = factor(%s$%s, levels=c(%s), labels=c(%s))\n',
df.name, variable, df.name, variable, old.str2, new.str2 ),
file = fn7, append = TRUE)
if (nchar(src[i]) == 0) {
break
}
old.str = new.str = NULL
variable = unlist(strsplit(src[i], " "))[2]
} else if (nchar(src[i]) != 0) {
field = unlist(strsplit(src[i], "\\\""))
n.field = length(field)
if (n.field == 2) {
old.str = c(old.str, as.integer(field[1]))
new.str = c(new.str, field[2])
} else if (n.field == 4) {
old.str = c(old.str, field[2])
new.str = c(new.str, field[4])
}
}
i = i + 1
}
invisible(df2)
}