cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

GitHub Sponsorsの募集を始めました、よろしくお願いします。

表題の通り、GitHub Sponsorsの審査を通過しましたので告知です。

要約

私のオープンソースソフトウェア(OSS)活動を支援するための寄付の受付を開始したので何卒一考ください。

f:id:u_ribo:20200327165250p:plain

きっかけ

GitHub Sponsorsは個人およびコミュニティのOSS活動を支援するための枠組みです。R界隈では世界の @Atsushi776 が始めたのが話題となりました(彼の告知記事。私もスポンサーになりました)。

私が得意とする言語はRです。学生時代から使っていて、まあ「ちょっとわかる」程度には使えると思っています。

Rはオープンソースで使うのにお金がかかりません。エディタとしても優秀な統合開発環境のRStudioアプリケーションも無料です。また、これまでに多くの書籍、プレゼンテーション、その他資料がインターネットで公開されてきました。

過去の人々の力に支えられている。その人たちは無償で行ってくれた。それなのに「お金儲け」をするのはどうなの?」という考えが少なからずあります。一方で仕事では前職から「ほぼR 100%」です。なので「お金稼ぎ」はしています。また書籍の執筆も少なからず行っており、それによる収入もあることも事実です。

ただ今年になって、そうした見方が変わってきました。Rは多くの研究、企業・業務でも使われる、素晴らしい言語です。そこには過去・現在・未来に豊富な人材があり、自分以上に生活の基盤としている人もいるでしょう(RStudio社はまさにそれ)。そして「技術はタダじゃない」そう思い至るようになりました。

自分の価値を低く見積もってはいけない。それはコミュニティ内の他のユーザや文化にも影響します。

なぜ支援を求めるのか

GitHub Sponsorsを始める前から、OSS活動を行う・幅を広げていく上でいくつかの課題がありました。それは次のようなものです。これらはいずれもお金で解決できる面があります。

  1. 英語力の向上
  2. 国際カンファレンス参加資金
  3. OSS参加のためのマシン、サーバ、サービス経費等 ... 現在の開発環境はiMac (2017) と MacBookPro (2013) がメインです。
  4. コミュニティ運営のモチベーション、活動経費
  5. R以外の言語、特にPython, Go, Juliaの学習

いくつかの項目について説明します。

OSS活動は、大別すると自分が開発者となり進めるもの、他の人・グループが中心にいる中で参画していくものがありますが、私はどちらかというと前者が中心で、グローバルなOSS活動についてはtypoの修正や簡単な機能実装等しか行えていないことが多く、まだまだ未熟さを感じています。そこには技術的な問題の他、「英語」の障壁が存在します(言語を問わず、コミュニケーション全般の課題でもありますが)。

言わずもがな、OSSでの中心言語は英語が主流です。1. 英語力の向上OSS活動を行う上で欠かせない能力だと捉えています。また次の2. 国際カンファレンス参加で他の開発者と交流する際も必要となります。

Rコミュニティには2つの大きな国際カンファレンスが毎年行われています。Rユーザの集会的なuseR!は、毎回開催箇所(国)が変わる面白さがあります。もう一つのRStudio conference (rstudio::conf) はRStudio社主催で本社のあるアメリカ開催です。

カンファレンスへの参加は先端の情報や意見交換ができるだけでなく、GitHubTwitterでお世話になっている人たちに会える絶好の機会です!一方でいずれも海外の開催&一週間ほどの滞在で個人で気軽に行くのには現実的な問題があります。(所属する企業でサポートがあると良いのですが、私の所属組織ではそうは行きません)

最後に4. コミュニティ運営のモチベーション、活動経費です。コミュニティを維持するためには時間もお金もかかります。私はTokyo.RおよびTsukuba.Rの2つのコミュニティに関わっています。と言っても、どちらもほとんど貢献できていません。

Tokyo.Rの方は運営メンバーではありませんが、Slackコミュニティのr-wakalangの保守とRユーザと話すpodcastR Radio for the Rest of us (R3)を継続しています。

また茨城県つくば市周辺のRユーザを対象にしたTsukuba.Rも再起動したものの、定期的に行える体制が整っていません。これは会場の確保をはじめとした各種の手続きに手間取ることが原因の一つです。コミュニティの形成は一朝一夕には行きませんが、ユーザを育てるためには欠かせないものだと信じています。私の怠惰で行えていないことは大いに反省すべきでしょう。一方でこれらの活動は完全に自らの時間と金を使って行われています。私のOSS活動の最終地点は、未来のためのユーザの育成(あるいは財産の共有)だと考えています。培った技術、情報は次世代に残していきたいです。

こんなことをしています

  • Rパッケージ開発
    • CRANに登録されているもの(jpmesh ⭐️26, zipangu ⭐️21, jpndistrict ⭐️11 fgdr ⭐️5)
    • 時々 rstudio関係、rOpenSci、r-spatialのパッケージへissueやPRを出します
  • 資料公開 (執筆、スライド、ブログ)
    • このブログQiitaの記事で使ったコードはリポジトリでも公開しています。またTokyo.Rをはじめとしたイベントでの講演資料もSpeakerdeckと共にアップロードしています。
    • 更新をサボりがちですが、空間解析や機械学習のための執筆をしています。

uribo.github.io

github.com

  • 可視化プロジェクト ... あれこれとやっています。地図を使ったものが多いです。可能な限りコードを公開しています。

f:id:u_ribo:20200327165449p:plain

  • 情報発信 ... 毎日GitHubに張り付いているので、新しいパッケージや機能追加は比較的早い段階でキャッチアップしています。スポンサーに情報共有するというのはアリかもしれません。

speakerdeck.com

speakerdeck.com

サポートする意思のある人へ

ここまであれこれと宣伝でした。ではいよいよスポンサー枠のご案内です。現在5つの枠を用意していますが、

  • $3/month または $6/monthは個人枠
  • $20/month, $50, $1000 ($100ではない)を企業枠と捉えています(もちろん個人でやってくれても大歓迎)

の2つが大きくあります。月単位でのサポートも可能です。その場合、サポートを停止するのをお忘れなく。

また、スポンサーになっていただけるのはどなたでも歓迎ですが、次の条件に合っているかどうか、踏みとどまって欲しい項目をあげておきます。

  • 私の開発するRパッケージ、資料を活用している人
  • 私を通じてコミュニティ活動を支えたい人
  • 私自身を応援したい人

スポンサーへの直接のお礼や特典は今のところありませんが、感謝の気持ちは述べさせてください。加えて、支援してくれる理由やお金の用途の指定があれば教えていただけますと幸いです。

それでは、瓜生真也と未来のRコミュニティのためにどうぞよろしくお願いします。

github.com

parzerパッケージで多様な緯度経度の表記を十進数に変換する

信頼と実績のrOpenSciから新しいパッケージがCRANに登録されました。parzerパッケージです。 このパッケージは多種多様な緯度経度の表記形式を処理し、十進数での表記(DEG: Degree)に変換してくれるものです。 (例えば「139°44’28.8869」を「139.7414」にする)

CRANリリース情報および基本的な関数の紹介はrOpenSciのブログ知りました。ぜひこちらもご覧ください。

ropensci.org

また、私自身も以前に同様の処理を行う方法としてこのような記事を書いています。 ですがparzerパッケージを使うとより簡単に緯度経度座標の表記を修正できます。 日本語の表記を扱う際には課題が残っているので、その対策を後述します。

まずは基本的な使い方を見ていきましょう。

install.packages("parzer")
library(parzer)

パース関数

入力された座標の値を十進数での表記に修正するパース関数はparse_*()で整備されています。 parse_lat()parse_lon()はそれぞれlatitude(緯度)、longitude(経度)を処理します。

日本経緯度原点の座標を例にします。

x <-  "E139°44’28.8869"
y <- "N35°39’29.1572"

parse_lon(x)
## [1] 139.7414
parse_lat(y)
## [1] 35.6581

パースした結果をマップ上で確認します。せっかくなので国土地理院地理院タイルを背景に。

library(sf)
library(leaflet)
library(sfheaders)
basemap <- 
  leaflet() %>%
  addTiles("http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",
           attribution = "<a href='http://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>")

sfheaders::sf_point(st_point(c(parse_lon(x), 
                               parse_lat(y)))) %>% 
  st_set_crs(value = 6668) %>% 
  st_transform(crs = 4326) %>% 
  mapview::mapview(map = basemap)

f:id:u_ribo:20200321130422j:plain

しっかりと「日本経緯度原点」にポイントが落ちていますね。

parse_*()は多様な表現方法に対応しています。

coords <- c(45.23323, "40:25:6N", "40°25’5.994N")
parse_lat(coords)
## [1] 45.23323 40.41833 40.41833

parse_lon_lat()は二つの引数に同じ長さの経度、緯度のベクトルを与えてデータフレーム形式で結果を返却します。

df <- 
  data.frame(
    lon = x,
    lat = y,
    stringsAsFactors = FALSE)

parse_lon_lat(df$lon, df$lat)
##   lon lat
## 1 139  35

また度分秒の要素を分解する関数として pz_*()があります。以下の例で度分秒それぞれの要素に分解します。

pz_degree(x)
## [1] 139

pz_minute(x)
## [1] 44

pz_second(x)
## [1] 28.90863

日本語での度分秒の処理

現在のバージョン(v0.1.0)ではUnicodeを扱う際には課題があります。

https://github.com/ropensci/parzer/issues/10 github.com

具体的には次のように日本語での「東経」「北緯」、「度分秒」を扱う際に発生する問題です。

x <- "東経139度44分28秒8869"
y <- "北緯35度39分29秒1572"

x %>% 
  parse_lon()
##  [1] NaN
y %>% 
  parse_lat()
##  [1] NaN

うまくパースされません。これに対して簡単な方法ですが、「度」「分」「秒」を変換しておきます。そうすると正常にパースできます。

@Atsushi776 さんに効率的な置換の方法を教えてもらったので書き直しています👇

library(stringr)
x_res <- 
  x %>% 
  str_replace("東経", "E") %>% 
  str_replace_all(c("度" = "\u00b0", "分" = "\u2019", "秒" = "."))
x_res
## [1] "E139°44’28.8869"
parse_lon(x_res)
## [1] 139.7414

y %>% 
  str_replace("北緯", "N") %>% 
  str_replace_all(c("度" = "\u00b0", "分" = "\u2019", "秒" = ".")) %>% 
  parse_lat()
## [1] 35.6581

さらに関数化するなら以下のような感じで。

replace_dohunbyo_kanji <- function(x) {
  str_replace_all(x, c("東経" = "E", "西経" = "W",
                    "北緯" = "N", "南緯" = "S"), 
               c("E", "W",
                 "N", "S")) %>% 
    str_replace_all(c("度" = "\u00b0", "分" = "\u2019", "秒" = "."))
}

replace_dohunbyo_kanji(x)
## [1] "E139°44’28.8869"

replace_dohunbyo_kanji(y) %>% 
  parse_lat()
## [1] 35.6581

Enjoy!

データフレーム上の緯度と経度を空間オブジェクトに変換する-🦉sfと🐍geopandasの例-

地理空間データを取り扱う際は、はじめにgeojsonやshapeファイルで受け取ることが一般的かと思います。 あるいはポイントデータの場合には、緯度と経度の値が各列に記録されるcsvなどの表形式のテキストファイルを起点とすることもあります。

前者のような地理空間データであれば、 R等のアプリケーションで読み込めば自動的に地理空間データとみなしてくれますが、 後者の場合はそうではありません。 座標の値はあくまでも数値です。なのでこうしたデータを地理空間データとして扱えるようにするには変換作業が必要となります。

今回の記事では、RおよびPythonでのデータフレームに記録された緯度経度の列を変換し、空間オブジェクトとして扱えるようにする方法を紹介します。 空間オブジェクトの形式としてRではsf、Pythonではgeopandasを対象にします。地理空間データに変換しておくと、データの空間的な配置を可視化可能になったり空間演算が可能になり、分析の幅を広げることが期待できます。

対象データ

csvファイルに緯度と経度の値が格納されているデータの例として、国土交通省 位置参照情報ダウンロードサービス 提供の平成30年整備 茨城県つくば市の大字・町丁目レベルのデータを利用します。

ファイルをダウンロードしたら、まずはデータフレームとしてcsvファイルを読み込みましょう。

library(sf)
library(readr)
df_isj08220 <- 
  read_csv("08220_2018.csv",
         locale = locale(encoding = "cp932"),
         col_types = "___c_cdd__") %>% 
  purrr::set_names(c("city", "street_lv1", "latitude", "longitude"))

データの一部を表示します。latitude, longitudeの列がそれぞれ独立しています。

city street_lv1 latitude longitude
つくば市 赤塚 36.04268 140.1232
つくば市 明石 36.18207 140.0537
つくば市 36.11289 140.0725
つくば市 安食 36.16775 140.0163
つくば市 あしび野 35.96060 140.1122
つくば市 吾妻一丁目 36.08153 140.1129

sfパッケージ

sfパッケージでデータフレームの座標情報を参照してポイントデータに変換するにはst_as_sf()を使います。 第一引数に座標列を含む対象のデータフレーム、coords引数の値にX,Y座標となる列名を指定します。この時、座標参照系が判明している際はcrs引数によって明示しておきましょう。

sf_isj08220 <- 
  df_isj08220 %>% 
    sf::st_as_sf(coords = c("longitude", "latitude"), crs = 4326)
sf_isj08220
## Simple feature collection with 299 features and 2 fields
## geometry type:  POINT
## dimension:      XY
## bbox:           xmin: 140.0102 ymin: 35.95047 xmax: 140.1676 ymax: 36.22462
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
## # A tibble: 299 x 3
##    city     street_lv1              geometry
##    <chr>    <chr>                <POINT [°]>
##  1 つくば市 赤塚         (140.1232 36.04268)
##  2 つくば市 明石         (140.0537 36.18207)
##  3 つくば市 旭           (140.0725 36.11289)
##  4 つくば市 安食         (140.0163 36.16775)
##  5 つくば市 あしび野      (140.1122 35.9606)
##  6 つくば市 吾妻一丁目   (140.1129 36.08153)
##  7 つくば市 吾妻二丁目   (140.1111 36.08443)
##  8 つくば市 吾妻三丁目    (140.1109 36.0889)
##  9 つくば市 吾妻四丁目   (140.1183 36.08722)
## 10 つくば市 天久保一丁目  (140.1087 36.0919)
## # … with 289 more rows

緯度経度の列を元にポイントデータが作成できました。地図上にマッピングしてみます。

mapview::mapview(sf_isj08220)

f:id:u_ribo:20200309165213j:plain

sfheadersパッケージ

続いてsfheadersパッケージを使う方法です。sf::st_as_sf()の実行結果と同じく、sfオブジェクトを作成します。 今回は対象がポイントデータなので、sf_point()のx,y引数にそれぞれ経度と緯度の列名を与えます。 この時、keep引数の値をTRUEにしておくとデータフレームの他の列が残ります。FALSEではポイントのgeometry列のみしか残らないため注意です。 また、座標参照系もこのタイミングでは指定できないため、sf::st_set_crs()で定義する必要が生じます。

df_isj08220 %>% 
  sfheaders::sf_point(x = "longitude", y = "latitude", keep = TRUE) %>% 
  st_set_crs(4326) %>% 
  tibble::new_tibble(subclass = "sf", nrow = nrow(.))
sf_isj08220sfh
## Simple feature collection with 299 features and 2 fields
## geometry type:  POINT
## dimension:      XY
## bbox:           xmin: 140.0102 ymin: 35.95047 xmax: 140.1676 ymax: 36.22462
## z_range:        zmin: NA zmax: NA
## m_range:        mmin: NA mmax: NA
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
## # A tibble: 299 x 3
##    city     street_lv1              geometry
##    <chr>    <chr>                <POINT [°]>
##  1 つくば市 赤塚         (140.1232 36.04268)
##  2 つくば市 明石         (140.0537 36.18207)
##  3 つくば市 旭           (140.0725 36.11289)
##  4 つくば市 安食         (140.0163 36.16775)
##  5 つくば市 あしび野      (140.1122 35.9606)
##  6 つくば市 吾妻一丁目   (140.1129 36.08153)
##  7 つくば市 吾妻二丁目   (140.1111 36.08443)
##  8 つくば市 吾妻三丁目    (140.1109 36.0889)
##  9 つくば市 吾妻四丁目   (140.1183 36.08722)
## 10 つくば市 天久保一丁目  (140.1087 36.0919)
## # … with 289 more rows

おまけ: sfからdfに戻す

データフレーム(df)の緯度経度情報をもとにsfオブジェクトを作成する例を述べましたが、反対にsfからデータフレームへ、地理空間情報を取り除く方法も紹介します。

作成したsfオブジェクトに対してsf::st_drop_geometry()ないしsf::st_set_geometry(value = NULL)でgeometry情報を除外することでdfに変換されます。

sf_isj08220 %>% 
  st_drop_geometry()
sf_isj08220 %>% 
  st_set_geometry(value = NULL)

この時、ポイントの情報も失われるので、データフレームの列として残したい場合は予め列を追加する必要が生じます。

sf_isj08220 %>% 
  dplyr::mutate(coords = purrr::map(geometry,
                                    ~ as.data.frame(sf::st_coordinates(.x)))) %>% 
  tidyr::unnest(cols = coords) %>% 
  st_drop_geometry()
## # A tibble: 299 x 4
##    city     street_lv1       X     Y
##  * <chr>    <chr>        <dbl> <dbl>
##  1 つくば市 赤塚          140.  36.0
##  2 つくば市 明石          140.  36.2
##  3 つくば市 旭            140.  36.1
##  4 つくば市 安食          140.  36.2
##  5 つくば市 あしび野      140.  36.0
##  6 つくば市 吾妻一丁目    140.  36.1
##  7 つくば市 吾妻二丁目    140.  36.1
##  8 つくば市 吾妻三丁目    140.  36.1
##  9 つくば市 吾妻四丁目    140.  36.1
## 10 つくば市 天久保一丁目  140.  36.1
## # … with 289 more rows

Python

import pandas as pd
import geopandas
df = pd.read_csv("08220_2018.csv",
                 encoding = "cp932",
                 usecols = [3, 5, 6, 7],
                 dtype  = {'市区町村名': str, '大字町丁目名': str, '緯度': float, '経度': float})
df = df.rename(columns = {'市区町村名': 'city', '大字町丁目名': 'street_lv1', '緯度': 'latitude', '経度': 'longitude'})

geopandas

gdf = geopandas.GeoDataFrame(df, geometry = geopandas.points_from_xy(df.longitude, df.latitude))

gdf.head(10)
city street_lv1 longitude latitude geometry
つくば市 赤塚 140.1 36.04 POINT (140.12315 36.042677)
つくば市 明石 140.1 36.18 POINT (140.053737 36.182065)
つくば市 140.1 36.11 POINT (140.072495 36.11289)
つくば市 安食 140.0 36.17 POINT (140.016345 36.167751)
つくば市 あしび野 140.1 35.96 POINT (140.112185 35.960604)
つくば市 吾妻一丁目 140.1 36.08 POINT (140.112938 36.081534)
つくば市 吾妻二丁目 140.1 36.08 POINT (140.111144 36.084425)
つくば市 吾妻三丁目 140.1 36.09 POINT (140.11092 36.088898)
つくば市 吾妻四丁目 140.1 36.09 POINT (140.118261 36.087219)
つくば市 天久保一丁目 140.1 36.09 POINT (140.108708 36.091901)

せっかくなのでこちらもマッピング

import geopatra
m = gdf.folium.plot(zoom = 10)
m

f:id:u_ribo:20200309165238j:plain

いいですね!Enjoy!