horioの雑記帳

データ分析屋さんが、気の向いた事を色々メモってる雑記帳。

取得データからデータマートを作ろうとしてみた

※この記事は R Advent Calendar 2014 - Qiita の 19日目の記事です。

 前編RでXBRLデータを取得してみた - horioの雑記帳では、XBRL形式での有価証券報告書を、zip形式でローカルに保存しました。
 後編の本ポストでは、保存したzipから、表形式での財務諸表データを作成してゆきます。

前・後編全体を通した手順

 前編の再掲ですが、このような流れです。
f:id:horihorio:20141215223656p:plain

(前編)RでXBRLデータを取得してみた → ここRでXBRLデータを取得してみた - horioの雑記帳
(後編)取得データからデータマートを作ってみた → 本ポスト

プログラム本体

 今回も、著者より献本頂いた本のソースが下敷きになっております。下記リンクより、書籍に掲載されいるサンプルコードのDLも可能です。

1. 書籍のサンプルコードからの改変点

  • サンプルコードでの「サンプルインスタンス」を、解凍したzip下の"*.xbrl"に置き換える
  • 日本語名称は、横持ちさせてから最後に紐付ければ良いので省略
  • 連結・単体の両方の要素を取得するように変更。理由は以下の通り
    • 単体=連結の企業の場合、連結の要素が全部NULLの場合があるから
    • 両方でも一方でも計算負荷はほぼ一緒、かつ後で削除するのは簡単なので
  • 処理のボトルネックはCPUなので、福島氏の著書や大仏様のお告げを参考に、並列処理に変更



2. 使い方

以下条件を満たせば、コピペでそのまま動きます。なお今度は、外部サービスへの迷惑はかからないので一安心です。

  • 前編でDLしたzipが、プログラムを置いたディレクトリ直下の"data"フォルダー下に全てあること

3. プログラム

INPUT
  • 前編でDLしたzip
OUTPUT
  • "definfo.txt" -> ID、名称、連結・単体の区分、会計期間。中身はこんな感じ
"ED2013062600002"	"株式会社トランスジェニック"	NA	"CurrentYearNonConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600002"	"株式会社トランスジェニック"	NA	"CurrentYearConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600003"	"コムテック株式会社"	NA	"CurrentYearNonConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600003"	"コムテック株式会社"	NA	"CurrentYearConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600004"	"株式会社DTS"	NA	"CurrentYearNonConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600004"	"株式会社DTS"	NA	"CurrentYearConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600005"	"株式会社フライングガーデン"	NA	"CurrentYearNonConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600006"	"㈱寺岡製作所"	"TERAOKA SEISAKUSHO CO., LTD"	"CurrentYearConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600006"	"㈱寺岡製作所"	"TERAOKA SEISAKUSHO CO., LTD"	"CurrentYearNonConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600007"	"(株)大戸屋ホールディングス"	"OOTOYA Holdings Co., Ltd."	"CurrentYearConsolidatedDuration"	"2012-04-01"	"2013-03-31"
"ED2013062600007"	"(株)大戸屋ホールディングス"	"OOTOYA Holdings Co., Ltd."	"CurrentYearNonConsolidatedDuration"	"2012-04-01"	"2013-03-31"
  • "FI.CurrentYear.txt" -> DLした縦持ち財務諸表。中身はこんな感じ
"ED2013062600002"	"jpfr-t-cte_IssuanceOfNewSharesExerciseOfSubscriptionRightsToSharesNA"	"CurrentYearNonConsolidatedDuration"	"JPY"	"2186000"
"ED2013062600002"	"jpfr-t-cte_NetIncomeNA"	"CurrentYearNonConsolidatedDuration"	"JPY"	"18877000"
"ED2013062600002"	"jpfr-t-cte_NetChangesOfItemsOtherThanShareholdersEquityNA"	"CurrentYearNonConsolidatedDuration"	"JPY"	"-4391000"
"ED2013062600002"	"jpfr-t-cte_TotalChangesOfItemsDuringThePeriodNA"	"CurrentYearNonConsolidatedDuration"	"JPY"	"16672000"
"ED2013062600002"	"jpfr-t-cte_IssuanceOfNewSharesExerciseOfSubscriptionRightsToSharesCAP"	"CurrentYearNonConsolidatedDuration"	"JPY"	"1093000"
"ED2013062600002"	"jpfr-t-cte_TotalChangesOfItemsDuringThePeriodCAP"	"CurrentYearNonConsolidatedDuration"	"JPY"	"1093000"
ソースコード:"02.perseXBRL.R"
rm(list=ls())
invisible(gc()); invisible(gc())

##### library #####
library("XBRL")
library("foreach")
library("doParallel")

##### prerequirement #####
setwd("./data/")
# unzip対象リスト取得
str.filelist <- dir(pattern = "*zip")

# 並列処理準備
n.cores <- detectCores()
cl <- makeCluster(n.cores, type = "PSOCK")
registerDoParallel(cl)

##### main #####
rm(f.export.persed)
f.export.persed <- function(str.filelist, i){
### zip2xbrl
  # zip内ファイル名取得
  wk.file.list <- unzip(zipfile = str.filelist[i] ,list = TRUE)
  # zipファイル解凍
  unzip(zipfile = str.filelist[i])
  unlink("XbrlDlInfo.csv") # 不要なので即消去
  
  # 拡張子が"xbrl"のファイル名を取得し整形
  wk.file.xbrl <- wk.file.list[grep("xbrl", wk.file.list[,1]), 1]
  wk.file.xbrl <- paste0("./", wk.file.xbrl)
  ### xbrlをparse ### この一行が非常に重い! ###
  xbrl.parsed <- xbrlDoAll(wk.file.xbrl) 
  
  # cleanup
  # 解凍フォルダを削除
  wk.dirname <- unlist(strsplit(wk.file.xbrl,"/"))[2]
  unlink(wk.dirname, recursive = TRUE)
  # 一時変数を全削除
  rm(list = ls(pattern = "wk*"))

### 文書情報、財務諸表情報を抽出
  dat.facts <- xbrl.parsed$fact
  # 内容(=facts) の文字コードを,UTF-8 からShift-JIS に修正
  dat.facts$fact <- iconv(x=dat.facts$fact,from="UTF-8",to="Shift-JIS")
  
### 文章情報
  # 開示者名称を抽出(日英両方)
  target <- grepl(pattern="^DocumentInfo",x=dat.facts$contextId)
  dat.Presenter.Info <- dat.facts[target,]
  dat.Presenter.Info <- dat.Presenter.Info[grepl(pattern="^jpfr-di_EntityName", x=dat.Presenter.Info$elementId), "fact"]
  
  # 期首・期末日を抽出(連結・単体)
  dat.contexts <- xbrl.parsed$context
  target <- grepl(pattern="^CurrentYear",x=dat.contexts$contextId)
  dat.contexts <- na.omit(dat.contexts[target, c("contextId","startDate","endDate")])
  
  # 解凍元ファイル名をKeyとして付与
  dat.definfo <- cbind(sub(".zip","",str.filelist[i]), dat.Presenter.Info[1], dat.Presenter.Info[2], dat.contexts)
  # ファイルに追記出力
  write.table(dat.definfo, "../definfo.txt"
              , sep = "\t", col.name = FALSE, row.names = FALSE, append = TRUE)

### 財務諸表データ
  # 当期要素を抽出(連結・単体)
  target <- grepl(pattern="^CurrentYear",x=dat.facts$contextId)
  dat.FI.CurrentYear <- dat.facts[target,]
  # 解凍元ファイル名をKeyとして付与
  dat.FI.CurrentYear <- cbind(sub(".zip","",str.filelist[i]), dat.FI.CurrentYear[,c(1:4)])
  # ファイルに追記出力
  write.table(dat.FI.CurrentYear, "../FI.CurrentYear.txt"
              , sep = "\t", row.names = FALSE, col.name = FALSE, append = TRUE)
  
### cleanup(これをしないとメモリーが逼迫)  
  rm(list = ls(pattern = "^dat|target|^xbrl"))
  invisible(gc()); invisible(gc())

### 経過出力
  print( c(i, format(Sys.time(), "%m/%d %T")) )
} 

##### main #####
foreach(i = 1:length(str.filelist)
        , .export = ls(envir=parent.frame())
        , .packages = c("XBRL") ) %dopar% {f.export.persed(str.filelist, i)}

stopCluster(cl)

上記プログラムの問題点

 以下4点、個人的な対応コスト順に挙げます。一部の問題は、XBRL仕様書を読めば解決しそうな気がします*1

1. 今年以降に提出される、次世代EDINETタクソノミに未対応

 次世代EDINETタクソノミは何か?は、金融庁HPをご参照下さい。
 重要な点は、上記プログラムは過去分のみにしか通用しないことです。ですので、次世代EDINETタクソノミ用のプログラムを用意する必要があります。なんか悪い予感はしており…。
 また、今年の提出物を眺めると、過去形式で提出された模様なのが散見されます。なので、zipを解凍した結果を見てから、適用するのは 上記ソース/新規のプログラム を判定するロジックも考えないといけません。ただ、数ファイルを実際に眺める限りだと、ロジックと実装は簡単そうな気がしております。

2. IFRS国際会計基準)未対応

 日本でのIFRS適用第1号:日本電波工業(6779)の2013年については、上記ソースではダメでした。1.と同じ話なのかも、も含め、まだ調べておりません。

3. 会計基準が分からない

 現在日本では、日本/米国/IFRSの3基準から選択可能ですが、XBRLのみの情報で、会計基準を判断する情報がまだ分かっておりません。
 なお、どの会計基準なのか?の情報は非常に重要です。例えば売上高については*2、以下リンクの住友商事JTを見ると、会計基準だけで数倍のオーダーで数字が変わります*3。よって、考慮しない訳にはいきません。

4. xbrl::xbrlDoAllが激重

 上記プログラムの処理時間は、9割程度がxbrl::xbrlDoAllになります。CPU勝負なので、AWSのc3.largeで2並列処理をかけてみると、1ファイル平均で20秒程度になります*4東証1/2部、JASDAQ、マザーズで上場企業は約3400社あるので、全企業1年分ならば3/4日ですか。
解決策としては、次の2点を考えてます。

  • AWSにお金を積む

 高級インスタンス借りれば?と思い実験もしましたが、vCPU/ECUが上がるほど処理は早くなるものの、やはり時間短縮は収穫逓減でした。よってお財布との天秤の結果、コンピューティング最適化シリーズで最安のc3.largeにしました。
ついでに無料枠のt2.microも実験しましたが、1日の処理数が100との計算でした…。精神衛生上、やらないことにしました。

  • xbrlDoAllを使わず、XMLで実装する

 取得したい項目が決まっているのだから、この筋でも行けるかも?とも思っています。次世代EDINETタクソノミ対応の際に、気が向けば挑戦するかもしれません。

と、12/17夜時点で下書きしてましたが

 12/18朝の通勤電車内で衝撃が。

 arelleは知らなかったですし、Elasticsearch面白そう、金融データで遊べるのは面白そう、とかとか思ったのと同時に、心からポキッと音がしたのでした。。。
 ただ、よくよく記事を読むと、情報の取得源は有料なのでしょう(多分)。XBRLでどんな情報が不足とか変とか、出来る事を何とかやってみよう、と自分に言い聞かせるのでした。

5. FI.CurrentYear.txt(縦持ち財務諸表)の横持ち変換ソース、各種結合を書いていない

 全上場企業約3400社を1年間ならば、ファイルサイズは120M程度の計算です。この程度ならばRでも出来るかも?と勝手に思っています。出来ないならば、Perl/Pythonの召喚とか。
 各種の出力の結合については、id([id名].zip が随所に登場)が各出力データにあるので、その項目を用いて各種結合を行う予定です。

感想

  • Elasticsearchの記事で、心がぽっきり折れかかっております
  • こういうときは、オレ何やりたかったのだっけ?を今一度考え直すことでしょうか。年末、旅に出ます。。。

Disclaimer

本記事は個人的な勉強のために作成したものです。本情報の内容については万全を期しておりますが、その内容を保証するものではありません。これらの情報によって生じたいかなる損害についても、筆者は一切の責任を負いません。
なお、本資料における意見、見解等はすべて筆者の個人的なものであり、筆者の属する組織の意見、見解等ではないことをご了承ください。

*1:これまで読まずにやってきたのかい!とのツッコミは正論です。はい。

*2:適当にググった結果なので数字が古いです

*3:数倍も違う理由は、リンク先のとおりです。商品原価の大半が税金になる商材の場合には良くある話です。

*4:処理時間の計算は、私の目算w