niszetの日記

アナログCMOS系雑用エンジニアが頑張る備忘録系日記

(Pandoc) Pandocでdocxなどのバイナリ形式のファイルのテンプレートを出力するときは>ではなくて-oを使おう

一応書いておく

ちょっと前のissueですが、Pandocでテンプレートを出力する--print-default-data-fileを使う際に pandoc --print-default-data-file reference.docx > custom-reference.docxで出力するとファイルが壊れることがあります(下記issue)

github.com

この回避方法として、>で出力先をファイルに変更するのではなく、-oオプションを使って出力先ファイル名を指定することでこの問題を回避できるようになりました。

まぁ実際のところテンプレートを --print-default-data-fileで出力すると抜けるスタイルがあるのでおススメしないですが…(これissueに上げておくべきなのかな…といっても誰も困ってなさそう)

他の形式、たとえばodtやpptxも、-oで出力した方が良いでしょう。

(Pandoc) Emojiは外部のライブラリに切り出されていた(メモ)

収録している(?)絵文字の数が格段に増えてました

以前Pandocのemojiについて調べていた時は、Pandoc本体に取り込まれていて対応する文字の数も少なかったのですが、下記のcommitで外部のパッケージに切り出されていたようです。

github.com

そのライブラリ自体はこちら。

hackage.haskell.org

対応しているemojiの名前はこのファイルに書かれているので、困ったらこのファイルを見ましょう(欲しい絵文字が登録されていないならPR出すとかですね)

github.com

(Pandoc) Pandocは脚注の設定をreference.docxから持ってこれない

...と思う。

Pandocのバージョンは2.11.4です。いつの間にこんなにバージョン上がってるの…。

久々にPandocのissueでも見るかーって見た一個目がこれだったので、これをやることにした。

github.com

Wordの脚注の線と脚注の文字の間のスペースを詰めたいという話。

まずはこの脚注の線を修正する方法。ググれば出てくるが、

  1. 下書きモードの表示にする
  2. ツールバーの参考資料から注の表示を選択して表示させる
  3. 出てきた脚注のなかで脚注の境界線を選択する。

すると、よくわからんが線が表示される。これを消して他の文字にするなどすれば脚注の線は他の文字に変えることが出来る。これ以外の修正方法があるのかは知らない。

なお、この情報はdocx内ではfootnotes.xml中に含まれており、専用のタグ、<w:separator />を使って示されている。もう一個の方は<w:continuationSeparator />である。<w:footnote w:type="separator" w:id="-1">などで調べると良い。w:idは他の数字かもしれないので注意。

さて、この脚注だが実はこの画面上で右クリックすることでフォントやスタイル、段落設定をすることが可能である。そのため、段落設定で段落後のスペースを0にすることによって、脚注の区切り線と脚注の文字との間を詰めることが可能なのである。

脚注の設定はココでしか使わないので、そのまま指定しても良いが、使いまわしを考えるならばスタイルを専用に作成して指定する方が良いだろうと思う。とはいえ、reference.docx中のスタイルを別のファイルにマージするとか面倒くさいのであまりやることはないと思うだが…。

さて一方でPandocはこの脚注の設定をreference.docxから拾ってくれないようだ。実行した結果がそうなのだからそうなんだろう。footnotes.xmlから設定を引っこ抜いてくるためには、styles.xmlからスタイルを引っこ抜いてくるのと同様の処理をしないといけないであろう。

ということで、あとでissueに書いたコメントを見直してよくわからなかったとき用にメモを残しておく。真面目にキャプチャとか載せておけばもっといい記事になるのに面倒くさいのでやらない。誰か書いてくれ。

(R) Windows上で parzer パッケージが動かないやつの暫定対応

なんでしょうね

ちょっとkuniezuパッケージを試してみたかったんですが、

github.com

内部で使ってるparzerパッケージがエラー吐いて止まる(Win、32bit版)か止まってしまって処理が返ってこない(Win, 64bit版)という挙動をしていました。

再現できるコードはこれ。

parzer::parse_lon("10")

RToolsは4.0の最新版を入れなおしてもダメでした。一方、Ubuntu上では問題なく動作して、結果として一個前の記事が出来ました(ヨカッタネ)

ってことをTwitterに呟いていたら、僕らのユタ兄さんから、

というアドバイスをいただいて、上記コード実行前に

Sys.setlocale(locale = "C") 

をしたら動くようにはなりました。が、原因がわからないのでちょっと調べてみます。昔のバージョンなら問題なかったのではないだろうか、とかそのあたりですね…(CRANのテストはもちろん通った状態でリリースされているのでそこら辺ではなさそう)

続報

その後、std::regexが問題とわかり…

結局、gccに問題があるのではということまで来ました(主にユタ兄さんの解析によって)

現状では先にあげたように、localeを設定してやり過ごすのが対策になりそうです。ちなみに、regex[]がダメっぽくて、[0]はダメ、(a+)*\dみたいなのはOKでした。こんなこと(コンパイラのバグ)ってあるんですねぇ…(Windowsの日本語localeのみで発生するregexのバグ…)

ちなみに、Windows上でSys.getlocale()するとこうなります。この環境下でのみ発生する、という感じですね(ubuntuのutf8環境では問題なかった)

> Sys.getlocale()
[1] "LC_COLLATE=Japanese_Japan.932;LC_CTYPE=Japanese_Japan.932;LC_MONETARY=Japanese_Japan.932;LC_NUMERIC=C;LC_TIME=Japanese_Japan.932"

(R) Ubuntu 20.04 LTS に RStudio Server をインストールしようとしたらlibssl.so.1.0.0がないと怒られたので対応してインストールするやつ

絶対忘れるのでメモ。

Ubuntu18の方で普通にRをインストールするとR3.xが入ってしまうので、Ubuntu20.04LTSに移行しようとした。

で、Rを入れた後にRStudioをこのページに書かれているコマンドで入れようとしたのだが、

rstudio.com

以下のようなエラーが出てRStudio serverは起動できなかった。

Couldn't find an alternative telinit implementation to spawn.
/usr/lib/rstudio-server/bin/rserver: error while loading shared libraries: libssl.so.1.0.0: cannot open shared object file: No such file or directory

調べたら下記のサイトに書かれていた。

askubuntu.com

投稿から引用すると、/etc/apt/sources.listdeb http://security.ubuntu.com/ubuntu xenial-security main という行を追記し、その後 sudo apt update して sudo apt install libssl1.0.0 してから sudo rstudio-server start して起動できることを確認しました。

とりあえず良かった~

(R)震源レコードフォーマットを読み込む

固定文字長フォーマットの読み込み。

各日のページからデータを読み込むことが出来たので不要になったが、これが出来ないと思っていたので過去の震源データを下記から入手して確認しようとしていた。

www.data.jma.go.jp

欲しい年を選択すると、例えば2016年ならh2016.zipみたいなファイルがダウンロードできる。これを展開すれば2016なるファイルがある。拡張子はないが、これは文字長固定で区切られたファイルで、csvなどのカンマや空白で区切られたファイルではない。

このファイルフォーマットの説明はこちら。

www.data.jma.go.jp

これを読み混むことにする。これはreadr::read_lines()stringr::str_sub()を組み合わせれば出来る。

  ff <- readr::read_lines("h2016")

fx <- function(f){
  d <- data.frame(
  record = stringr::str_sub(f, 1, 1), # レコード種別ヘッダ
  year = stringr::str_sub(f, 2, 5),
  month = stringr::str_sub(f, 6, 7),
  day = stringr::str_sub(f, 8, 9),
  hour = stringr::str_sub(f, 10, 11),
  min = stringr::str_sub(f, 12, 13),
  sec = stringr::str_sub(f, 14, 17),
  err_sec = stringr::str_sub(f, 18, 21), # 標準誤差(秒)
  lat_deg = stringr::str_sub(f, 22, 24), # 緯度(度)
  lat_min = stringr::str_sub(f, 25, 28), # 緯度(分)
  err_lat_min = stringr::str_sub(f, 29, 32), # 標準誤差(分)
  long_deg = stringr::str_sub(f, 33, 36), # 経度(度)
  long_min = stringr::str_sub(f, 37, 40), # 経度(分)
  err_long_min = stringr::str_sub(f, 41, 44), # 標準誤差(分)
  depth = stringr::str_sub(f, 45, 49),
  err_depth = stringr::str_sub(f, 50, 52),
  magnitude1 =stringr::str_sub(f, 53, 54),
  magnitude1_type = stringr::str_sub(f, 55, 55),
  magnitude2 = stringr::str_sub(f, 56, 57),
  magnitude2_type = stringr::str_sub(f, 58, 58),
  used_soujihyo = stringr::str_sub(f, 59, 59),
  shingen_hyoka = stringr::str_sub(f, 60, 60),
  shingen_hojo = stringr::str_sub(f, 61, 61),
  max_shindo = stringr::str_sub(f, 62, 62),
  higai = stringr::str_sub(f, 63, 63),
  tsunami = stringr::str_sub(f, 64, 64),
  daichiku = stringr::str_sub(f, 65, 65),
  shochiku = stringr::str_sub(f, 66, 68),
  shino_chimei = stringr::str_sub(f, 69, 92),
  kansoku_tensu = stringr::str_sub(f, 93, 95),
  shingen_kettei_flag = stringr::str_sub(f, 96, 96)
  )
  d
}

fx(ff) -> df

これで良い。ただし得られるものは全て文字なので、適当に型変換する。depthは" 0 4" みたいなデータがあり、これを適切に変換する方法がわからないのでNAが返るようにしている(暗黙にNAになるので他のデータのエラーを見逃す可能性があるため注意。エラー処理を入れることをお勧めします。マグニチュードは例にCまでしか書いてなかったのでこのようにした。D以降があれば定義追加すればよいでしょう。 " " をNAとするかそのままにするかは好みかもしれません。NAでない場合の方が取り扱いやすいこともあるので。 たまに表示幅あわせで空白が入ってるケースがあるので注意。明示的にas.integerせずにwrite_csvしてread_csvしても、"00"は文字列の扱いなので注意、など細かい点に注意が必要です(下記では明示的に変換をしている)。 また、記号ではよくわからないもの(震度など)は別途変換関数を作ってあげると良さそうです(やってません)

fx2 <- function(df){
  df$year <- as.integer(df$year)

  df$month <- as.integer(df$month)
  df$day <- as.integer(df$day)
  df$hour <- as.integer(df$hour)
  df$min <- as.integer(df$min)
  df$sec <- as.integer(df$sec)/100
  df$err_sec <- as.integer(df$err_sec)/100
  df$lat_deg <- as.integer(str_remove(df$lat_deg, " ")) # remove space between "-" and number.
  df$lat_min <- as.integer(df$lat_min)/100
  df$err_lat_min <- as.integer(df$err_lat_min)/100
  df$long_deg <- as.integer(str_remove(df$long_deg, " ")) # remove space between "-" and number.
  df$long_min <- as.integer(df$long_min)/100
  df$err_long_min <- as.integer(df$err_long_min)/100

  df$depth = as.integer(depth)/100 # "注意。  0 4" みたいな謎フォーマットはNAになる。
  df$err_depth <- as.integer(df$err_depth)/100
  df$magnitude1 <- sup_magnitude(df$magnitude1)
  df$magnitude1_type = dplyr::na_if(df$magnitude1_type , " ")
  df$magnitude2 <- sup_magnitude(df$magnitude2)
  df$magnitude2_type = dplyr::na_if(df$magnitude2_type , " ")
  df$used_soujihyo = dplyr::na_if(df$used_soujihyo , " ")
  df$shingen_hyoka = dplyr::na_if(df$shingen_hyoka , " ")
  df$shingen_hojo = dplyr::na_if(df$shingen_hojo , " ")
  df$max_shindo = dplyr::na_if(df$max_shindo , " ")
  df$higai = dplyr::na_if(df$higai , " ")
  df$tsunami = dplyr::na_if(df$tsunami , " ")

  df$daichiku <- as.integer(df$daichiku)
  df$shochiku <- as.integer(df$shochiku)
  df$kansoku_tensu <- as.integer(df$kansoku_tensu)

  df
}

# 補助関数
sup_magnitude <- function(mag){
  mag <- stringr::str_replace(mag, "A", "-1")
  mag <- stringr::str_replace(mag, "B", "-2")
  mag <- stringr::str_replace(mag, "C", "-3")
  mag <- as.integer(mag)/10
  mag
}

fx2(df) -> df

(R)気象庁の震源リストからスクレイピング(コード一部更新した)

robots.txt とかも自分で一度は目を通しておこうね。

さて、以前の日記で気象庁の各日の震源データが消えていると思ったら見つかりました~ってのを書きました。

niszet.hatenablog.com

今回はそのページからデータを取ってくる作業をします。手作業でも良いかなと思ったらこれは日毎にページが分かれているわけですね~。流石にこれを手でやるのはちょっとシンドイので、久々にスクレイピングでやっつけます。

丁度良いことに、昨年末のJapan.R 2020にてwatagusaさんのLT、「"polite"で守るWebスクレイピングのエチケット」でpoliteパッケージの存在を知ったので、これを参考にしてやってみました。

Japan.Rのページはこちら

japanr.connpass.com

また、LTの内容ついてはwatagusaさんのブログにまとまってます。

watagusa.hatenablog.com

とりあえず先にコードを書いてしまうと、2016年の1年間のデータを取ってきて整形してdata.frameにして最後にcsvに書き出すコードは下記のようになります(1/14 1900更新)。

library(rvest)
library(xml2)
library(polite)
library(lubridate)
library(stringr)
library(dplyr)
library(magrittr)

trg <- lubridate::ymd("2016/1/1")
df <- data.frame()

session <- polite::bow(url=stringr::str_c("https://www.data.jma.go.jp/svd/eqev/data/daily_map/", sprintf("%04d%02d%02d", lubridate::year(trg), lubridate::month(trg), lubridate::day(trg)) ,".html"), delay=5)

while(trg < lubridate::ymd("2017/1/1")){

  session <- polite::nod(bow=session, path= stringr::str_c("https://www.data.jma.go.jp/svd/eqev/data/daily_map/", sprintf("%04d%02d%02d", lubridate::year(trg), lubridate::month(trg), lubridate::day(trg)) ,".html"))

  page <- polite::scrape(session)
  page %>% rvest::html_node(xpath = "/html/body/div[2]/div") %>%
    rvest::html_node("pre") %>% rvest::html_text() %>%
    readr::read_lines(skip_empty_rows = T, skip=3) %>%
    stringr::str_replace_all("° ", "°") %>%
    stringr::str_trim() %>%
    readr::read_delim(" ", col_names = F, trim_ws = T) %>%
    dplyr::bind_rows(df, .) -> df
  trg <- trg + 1
}

colnames(df) <- c("年", "月", "日", "時:分", "秒", "緯度", "経度", "深さ(km)", "M", "震央地名")

readr::write_csv(df, "2016.csv")

蛇足

以下、コードの補足。コピペでいいや~って人は読み飛ばしてどうぞ。

library()を先頭に書いているのでライブラリ名::関数名にする必要はないですが、どの関数がどのパッケージ由来かわからんくなるので書いてます。 ymd()を使っているのは日付の管理が面倒くさいので。trg <- trg + 1で日付を1日分進めています。これで2017/1/1以前の日について処理が回るというwhileループになっとります。 そもそも、URLがyyyymmddであるからそうなってますが。1月の場合に"01"としたい場合(0で埋めたい)場合はsprintf("%02d", day)みたいに書くのが一番楽だと思ってますが、今風の書き方はあるのかしら? politeの使い方はLTの資料とブログ記事を見てもらうとして、対象のデータがどこのタグにいるのかはブラウザの開発者ツールつかって掘っていく感じですね。この構造が未来永劫保たれているとも限らないし、他の年では違うかもしれないので注意。 readr::read_delim()ではなくreadr::read_lines()を使ってるのは後述するように変なところに空白があり、デリミタの指定で空白文字を使うと列がずれるため。とりあえず一旦読み込んで行ごとのデータにしたいならread_lines()が便利。最初の3行を取り込むと変なことになるのでskipし、列名はあとでつけるようにしています。 緯度経度でたまに一桁の数字があると表示位置のあわせでスペースを一個入れているようです。こういう目印になる文字がある場合は一緒に検出して置き換えればよいので楽。分かりづらいですが、"°"(度)の後に空白がある場合に"°"だけにしてスペースを取り除いています。2文字以上ある場合は対応しきれないので、[ ]+とかで検出かな。 stringr::str_trim()は各行末に位置合わせで空白文字があり、これが空列として追加されるのを防ぐ。 readr::read_delim()でスペースを区切り文字として読み取る。列名は不要。trim_wsで取り込み後に空白文字が残らないようになる。これをしないと、列の位置合わせで1月などは" 1"となっているため型が文字列になってしまう。10月は10なのでintegerと判断してくれる。そして型が合わずにbind_rowsで怒られる。 dplyr::bind_rows(df, .)は時系列順にしたいならこの順で。後でソートしても良いけど。 列名は"時:分"はオリジナルの列名とは異なるので、ここで与えている。決め打ちで良いところは決め打ちにしてしまう…。 最後にcsvに書き出してオシマイ。

使った感じ、politeパッケージは色々と面倒を見てくれるので便利ですね。Rでスクレイピングするなら今後は必ず使う方が良いんじゃないかなと思いました。watagusaさんありがとうございます。

rvestを使ったスクレイピングについてはほぼノー説明にしましたが、困ってる場合はRによるスクレイピング入門がおすすめですね。

www.c-r.com

lubridateパッケージについては書籍はないですが、ネット上に解説もあるので今回程度の使い方なら困ることはないでしょう(知らんけど)。

qiita.com

kazutan.github.io

さいごに

なんか変じゃねこれみたいなのあったら教えてくだされ~~

追記

nod使ってないじゃん!って気づいたので修正しました。修正前のコードも載せておきます(恥) nod使って何度もアクセスしないようにしようねって書いてあったのに、勢いで実装するとこうなるのだ…。気を付けよう。

library(rvest)
library(xml2)
library(polite)
library(lubridate)
library(stringr)
library(dplyr)
library(magrittr)

trg <- lubridate::ymd("2016/1/1")
df <- data.frame()

while(trg < lubridate::ymd("2017/1/1")){

  session <- polite::bow(url=stringr::str_c("https://www.data.jma.go.jp/svd/eqev/data/daily_map/", sprintf("%04d%02d%02d", lubridate::year(trg), lubridate::month(trg), lubridate::day(trg)) ,".html"), delay=5)

  page <- polite::scrape(session)
  page %>% rvest::html_node(xpath = "/html/body/div[2]/div") %>%
    rvest::html_node("pre") %>% rvest::html_text() %>%
    readr::read_lines(skip_empty_rows = T, skip=3) %>%
    stringr::str_replace_all("° ", "°") %>%
    stringr::str_trim() %>%
    readr::read_delim(" ", col_names = F, trim_ws = T) %>%
    dplyr::bind_rows(df, .) -> df
  trg <- trg + 1
}

colnames(df) <- c("年", "月", "日", "時:分", "秒", "緯度", "経度", "深さ(km)", "M", "震央地名")

readr::write_csv(df, "2016.csv")