2021年12月7日火曜日

メディア芸術DBクイズ のSPARQLクエリ解説

メディア芸術DBクイズ のSPARQLクエリについて解説します。 

まず「マンガクイズ」のためのクエリについて。
マンガクイズが最も単純な仕組みで動作しています。

1 まず以下のクエリを最初に1回だけリクエストして、対象となるデータの主語(?s)のレコード数を取得します。この結果をメモリに格納しておいて、それを最大値とした乱数をクイズ問題を作成するつど生成します。(この仕組みはアニメもゲームも同じ)
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <https://schema.org/>
SELECT (count(?s) as ?count)
WHERE {
?s schema:genre "マンガ単行本シリーズ" ;
   rdfs:label ?name ;
   schema:creator  ?creator ;
   schema:datePublished  ?time ;
   schema:publisher  ?publisher ;
   schema:brand  ?brand ;
   schema:numberOfItems  ?numberOfItems ;
   schema:inLanguage  "日本語" .
  FILTER regex(?creator, "")
  FILTER regex(?brand, "")
  FILTER (xsd:integer(?numberOfItems) >= 5)
}

2 次に以下のクエリで、実際にクイズに使うデータを取得します。
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <https://schema.org/>
SELECT *
WHERE {
?s schema:genre "マンガ単行本シリーズ" ;
   rdfs:label ?name ;
   schema:creator  ?creator ;
   schema:datePublished  ?time ;
   schema:publisher  ?publisher ;
   schema:brand  ?brand ;
   schema:numberOfItems  ?numberOfItems ;
   schema:inLanguage  "日本語" .
  FILTER regex(?creator, "")
  FILTER regex(?brand, "")
  FILTER (xsd:integer(?numberOfItems) >= 5)
} OFFSET 250
LIMIT 20

OFFSETの数字には、上記で生成した乱数をセットしてランダムにデータを取得するようにします。LIMITを20件にしている理由は、例えばマンガの作者を答える問題を出題した際、3択問題ですのでLIMIT 3、ということにすると、同じ作者がときどきかぶってしまうことがあります。LIMIT20にしておけば、万が一作者がかぶったとしても、そのときは20件のうちの別の作者に変えたらいいので、そういった保険的な意味合いで20件にしています。

■ データを取得する必須項目
?name  (作品名)
?creator  (著者)
?time  (出版年月)
?publisher  (出版者(社))
?brand  (出版ブランド名)
?numberOfItems  (発刊した巻数)

■ 絞り込み項目
?s schema:genre "マンガ単行本シリーズ" 
 ⇒「マンガ単行本シリーズ」データセットをターゲットとする
schema:inLanguage  "日本語"
 ⇒ 日本語のマンガだけをターゲットとする
FILTER regex(?creator, "") 
 ⇒ 言語指定されている「著者」を除外する
(※enを除外し単純リテラルのみ抽出)
FILTER regex(?brand, "") 
 ⇒ 言語指定されている「出版ブランド名」を除外する
(※enを除外し単純リテラルのみ抽出)
FILTER (xsd:integer(?numberOfItems) >= 5)
 ⇒ 単行本が5巻以上発刊されているマンガのみをターゲットにする
(※5巻以上の有名な作品のみにして問題を易しくするのが狙い)

あとは上記で取得した必須項目データを、乱数を組み込んだプログラムにより組み合わせを変化させてクイズを生成します。問題種別の出題割合は以下のとおりにしています。
・作品名を回答する問題 50%
・著者名を回答する問題 30%
・出版社を回答する問題 20%

アニメクイズもマンガと同じように最初に1回だけ最大値を取得し、あとはクイズのつど、クイズデータを取得します。以下はアニメデータを取得するクエリです。
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <https://schema.org/>
PREFIX ma: <https://mediaarts-db.bunka.go.jp/data/property/>
SELECT *
WHERE {
?s schema:genre "アニメテレビレギュラーシリーズ" ;
   rdfs:label ?name ;
   schema:productionCompany ?productionCompany  ;
   schema:actor ?actor  ;
   schema:contributor ?contributor  ;
   schema:startDate ?startDate  ;
   schema:endDate ?endDate .
 OPTIONAL{?s schema:publisher ?publisher.}
 OPTIONAL{?s ma:programDuration ?programDuration .}
 OPTIONAL{?s ma:originalWorkCreator ?originalWorkCreator .}
 OPTIONAL{?s ma:numberOfPrograms ?numberOfPrograms  .}
 OPTIONAL{?s ma:periodDisplayed ?periodDisplayed  .}
 OPTIONAL{?s schema:track ?track  .}           
}
ORDER BY ?startDate
OFFSET 400
LIMIT 20

クイズを作成する理屈は上述のマンガとほぼ同じですが、マンガと異なる点は、必須でない項目をOPTIONAL句で取得しているところ。例えば
 OPTIONAL{?s schema:track ?track  .}  
でアニメの主題歌を取得しています。
OPTIONAL句にしないと「データ不存在」の分の結果セットが減ってしまうので、データ不存在が多い項目についてはOPTIONALにしているということです。
OPTIONAL句の項目は、それが無くても問題として成立するけどヒント的な感じで出題文に入れ込む、というパターンが多いです。

ゲームクイズもだいたいマンガやアニメと同じ理屈で出題していますが、やや複雑な構造になっていますので詳しい説明は割愛します。以下のクエリでほぼ近いデータを得られます。
PREFIX schema: <https://schema.org/> 
PREFIX dcterms: <http://purl.org/dc/terms/> 
SELECT ?s ?gameName ?time ?gameGenre ?creator ?creatorName ?location ?character ?productionCompany ?productionCompanyName
WHERE { 
 ?s schema:name  ?gameName ;
    schema:datePublished  ?time ;
    schema:keywords  ?gameGenre . 
 FILTER regex(?gameName, "")  
 OPTIONAL {
   ?s dcterms:creator  ?creator . 
   ?creator schema:name  ?creatorName.
 } 
 OPTIONAL { ?s schema:contentLocation  ?location .} 
 OPTIONAL { ?s schema:character  ?character . } 
 OPTIONAL { 
   ?s schema:productionCompany ?productionCompany . 
   ?productionCompany schema:name  ?productionCompanyName.
 }
}

2019年10月30日水曜日

引っ越しました

わたくし、引っ越しました。
肉体の住まいの引っ越しではありません。
精神の住まいであるWEBサーバの引っ越しです。
非エンジニアの私が自力で引っ越しするのは大変でした。
住所はこちらです。 https://www.mirko.jp

なぜ引っ越そうと思ったか。
これまでは一般的な共用レンタルサーバに住んでいました。
年間3000円ちょっとの料金でサクサク動くし、PHPやPythonなどサーバサイドプログラムも動かせるし、インフラはプロに任せといたら安心だし、特段不自由を感じていませんでした。

でも次第にやりたいことが増えてきちゃうんですね。人の欲望は際限がない。
賃貸アパートでは壁に釘も打てないし、グランドピアノも置けない。

でも、自分の家に物理サーバを建てるのはさすがに無理(奥様を説得できない)

検討の結果、流行りのVPS仮想マシンを借りることにしました。
コンクリート打ちっぱなしで中身は全くの空っぽ、でも電気ガス水道は引いてある賃貸マンション、というイメージでしょうか。
仮想サーバでは Amazon AWS が有名ですが従量制なのでバズったときが怖い。
自治体消滅アプリを公開したときは毎日10万を超えるアクセスがあり、おしっこ漏れそうなくらいビビりました。(アプリはすぐ消滅してしまいましたが…)
よって、固定費用で、かつ京都の地元企業の KAGOYA にしました。

ちなみに、今まで使っていたレンタルサーバも京都の地元企業 ネットオウル です。
格安なのに安定したすばらしい速度でした。今までありがとう。
mirko.jp のドメイン管理はこれまでどおりネットオウルでやってもらおう。

で、VPSのサーバ構築ですが、まずOSを選ぶところからやるんですね。
僕はここ20年くらいWindowsしか触ってないのでUNIXの最近のトレンドを全く知りません。頭の中は FreeBSD か RedHat か、というところで止まっていました。
今は Ubuntu か CentOS のどっちにしよか、なんですね。
ググったところ、日本国内では CentOS のシェアが高いけど世界的には Ubuntu が人気らしい。意識高い系のグローバルな僕は Ubuntu を選びました。英語できないけど。

次にWEBサーバを作る。
WEBサーバといえばアパッチ。
アパッチのおたけび「ウラララーーー!!」
と叫びたくなったが、近年のトレンドはApacheではなくNginx(エンジンエックス)なんだって。
Nginxのほうが静的サイトの公開には抜群に強いらしい。
ということで、タッグパートナーのジェロニモを裏切りNginxと手を組む。

次に mirko.jpドメインの適用。
DNSサーバの設定はやったことなかったけど、なんとか完了。

次にWEBサーバにファイルを送り込む方法。
最近のトレンドはやっぱGitなんすかね。でもやっぱり馴染みのあるFTPがいいなぁ。
ということで、Gitは一旦横に置いておいて、vsftpdを導入。

次にPHP、MySql、phpMyAdminの導入。
ワードプレスは使っていないけど、PHPで動くアプリは沢山作っているので、僕にとってはこいつらは必須。

次にSSL証明書の導入。
今やSSLは現代人のマストアイテムになってしまいました。そして僕のような下級国民には無料の Let's Encrypt 一択。貧者の味方。
有効期限が3ヶ月しかないけど、CRONに書き込んでおけば自動更新もできちゃう。

ここまでが、僕にとっての必須項目。
でもせっかくなので他にもいろいろやってみる。

まずメールサーバをたてる。
僕がいつも使っているメールは、20年前から使っているプロバイダのやつ。
POP3しか対応していないので、いいかげんIMAPにしたいという思いがある。
かなり手こずったが、なんとか mirko.jpドメインのIMAPセキュアメールを作ることに成功。

で、いざメールを乗り換えようと思ったが、ここで問題点がわかる。
一つは、サーバがトラブったときにメールが使えなくなること。
素人が趣味でたてたサーバなのでときどき落ちる。WEBサイトがしばらく落ちても別にどうってことないけど、メールサーバが落ちて大事なメールがブラックホールに吸い込まれたらヤバい。
もう一つは、スパムのフィルターをかけるのが難しいこと。
人気者である僕のアドレスには、夫の莫大な遺産を持て余している未亡人やら、某アイドルの謎マネージャーやらから山ほどメールが届くので対策が必要なのだ。
頑張れば何とかなりそうな気もするが、僕の技術力ではちょっと難しいという印象。
ということで、目途がつくまではテスト扱いにする。残念。

次にグラフ型データベースの導入。
ミスターLODとしては、自分専用のRDFストアが欲しいところ。
これもかなり苦労したが、なんとかVirtuosoを導入することに成功。
僕んちのSPARQLエンドポイント
Endpoint URIは https://sparql.mirko.jp
今のところラーメンデータしか入れてませんが、よかったら遊んでみてね。
CORS対応、service句も使えます。

ここまでの総括
-------------------------------------------------------
かかった時間:10日間(約80時間)
費用:税込660円(月額。KAGOYA VPS KVMの1コア/1G版)
勉強になった度:★★★★★
得た満足感:プライスレス
代償:ひどい肩こり、腰痛、目の疲れ、家族の冷ややかな視線
-------------------------------------------------------

ここでクイズです。
普通に仕事しながらどうやって10日で80時間を捻出したのでしょうか?
答えはこれ。
平日 5時間×7日を捻出
休日 15時間×3日を捻出
睡眠時間を削りました。頑丈な体に産んでくれた親に感謝。
そして白い目で冷たく見守ってくれた家族にも感謝しよう。
今日は早く寝よ。おやすみ。

2019年9月18日水曜日

法人しりとりのSPARQLクエリ解説

法人しりとりは、経済産業省の 法人インフォ のSPARQL APIを利用したアプリである。
法人インフォでは、情報処理推進機構が提唱する 共通語彙基盤 を活用してデータモデルを作っており、RDFで実装されLODとして活用できるようになっている。
こちら が公開エンドポイント。
法人しりとりでは、以下のSPARQLクエリをリクエストし、しりとりに必要な情報を取得している。

PREFIX  hj: <http://hojin-info.go.jp/ns/domain/biz/1#>
PREFIX  ic: <http://imi.go.jp/ns/core/rdf#>
SELECT ?s ?corporateName ?corporateKana ?pref ?city
FROM <http://hojin-info.go.jp/graph/hojin>
WHERE{
  ?s hj:法人基本情報 ?key.
  ?key ic:名称 _:keyCorporateName .
     _:keyCorporateName ic:種別 '商号又は名称'.
     _:keyCorporateName ic:表記 ?corporateName .
     _:keyCorporateName ic:カナ表記 ?corporateKana .
  ?key ic:住所 _:keyAddress .
     _:keyAddress ic:種別 '住所' .
     _:keyAddress ic:都道府県 ?pref .
     _:keyAddress ic:市区町村 ?city .
  FILTER(regex(str(?corporateKana), '^ア' ))
}
LIMIT 1
OFFSET 300
(←0から300までの乱数をセット)

上記クエリで、
・法人番号(主語「s」から取得)
・法人名
・法人名カナ
・法人住所(都道府県/市区町村)

を取得する。

ポイントは
FILTER(regex(str(?corporateKana), '^ア' ))
のところ。
正規表現regex関数で、法人名カナの頭文字が「ア」のものを抽出する。
ここの「ア」を変更することにより、しりとりを継続していく。

悩むのがOFFSETの扱い。
しりとりゲームを成立させるにはランダムに法人を抽出する必要があり、OFFSETに整数の乱数をセットすることによりそれを実装する。
しかしながら先頭文字によって法人の数は大きく異なる(アから始まる法人の数とヌから始まる法人の数は大差がある)ので、大きな数をOFFSETにセットすると字によっては法人総数をオーバーしてしまうし、小さな数にすると抽出されない法人が多数出てくる字もある。
解決策は、以下のクエリで各文字の法人数を取得し、その値を最大値とする乱数をセットすること。

PREFIX  hj: <http://hojin-info.go.jp/ns/domain/biz/1#>
PREFIX  ic: <http://imi.go.jp/ns/core/rdf#>
SELECT (COUNT(?corporateKana) AS ?count)
FROM <http://hojin-info.go.jp/graph/hojin>
WHERE{
   ?s hj:法人基本情報 / ic:名称 / ic:カナ表記 ?corporateKana .
   FILTER(regex(str(?corporateKana), '^ア' ))
}


しかしながら上記のようにすると結局2回クエリを送信することになり、時間がかかり、しりとりのゲーム性が大きく損なわれる。
ゲームバランスを考えた結果、OFFSETは全ての文字で0~300で固定とすることとした。
(OFFSETが小さいほど処理が速くなる)

法人インフォのSPARQL APIのレスポンスがもっと速くなったら何の問題もないんですけどね…。


2019年9月4日水曜日

「自治体の未来」のSPARQLクエリ

「自治体の未来」は、政府統計総合窓口(e-Stat)の 統計LOD から、過去の統計調査結果のデータ(国勢調査・人口動態等)を取得しています。
以下のクエリを、統計LODのSPARQL Endpoint にリクエストすると、統計LOD「社会・人口統計体系データセット」から、特定の自治体のあらゆる統計データを取得することができます。当アプリもこのクエリを使っています。
------------------------------------------
PREFIX rdfs:<http://www.w3.org/2000/01/rdf-schema#>
PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/> 
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/> 
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#> 
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#> 
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/> 
select ?indicatorURI ?indicatorName ?year ?value 
where { 
?s  sdmx-dimension:refArea  sac:C26103-19700401 ; 
    cd-dimension:timePeriod  ?year ;  
    g00200502-dimension:indicator  ?indicatorURI ; 
    sdmx-measure:obsValue  ?value . 
?indicatorURI  rdfs:label  ?indicatorName . 
filter(LANG(?indicatorName)='ja') 
} ORDER BY ASC(?indicatorURI) ASC(?year) 
------------------------------------------

上記は「京都市左京区」の統計データを取得するクエリ。
sac:C26103-19700401 の部分が、京都市左京区の「期間付き標準地域コード」です。
この部分を変えることにより、他の自治体のデータを取得することができます。

(例)
C02201-20070901 青森市
C10201-20090505 前橋市
C13104-19830818 東京都新宿区
C13421-19700401 小笠原村
C23100-20030729 名古屋市
C18201-20060201 福井市
C26103-19700401 京都市左京区

以下のクエリで、2015年時点の全国の「期間付き標準地域コード」一覧を取得することができます。
------------------------------------------
PREFIX sdmx-dimension: <http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX qb: <http://purl.org/linked-data/cube#>
PREFIX sacs: <http://data.e-stat.go.jp/lod/terms/sacs#>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
select distinct ?code ?pref ?area
where {
?s  sdmx-dimension:refArea  ?code ;
    qb:dataSet  <http://data.e-stat.go.jp/lod/dataset/g00200521/d0003148521>.
?code  sacs:prefectureLabel  ?pref ;
       ic:表記  ?area ;
}order by ?code
------------------------------------------

2019年6月5日水曜日

横浜市保育所&幼稚園MAPのSPARQLクエリについて

このたび公開させていただいた 横浜市保育所&幼稚園MAP は、横浜市オープンデータポータルのWEB API(SPARQLエンドポイント)から必要なデータを取得している。

・横浜市WEB APIの説明は こちら
・横浜市SPARQLエンドポイントは こちら

このAPIデータは、共通語彙基盤の語彙とデータ構造が用いられている。保育所等のオープンデータを共通語彙基盤対応のLODとしたのはおそらく全国初であり、とても先駆的な取り組みといえる。

やや残念な点は、施設の座標情報(緯度経度)データがないところ。自分の通勤経路にマッチした保育施設を探すには、やはり座標情報が欲しい!

さて本稿では、横浜市APIで使えるSPARQLクエリと、それに対する考察を紹介する。

1 全ての施設のID、名称、区、住所を取得するクエリ

select *
where {
 ?s ic:ID [ic:識別値 ?id ]  ;
    ic:住所 [ ic:区 ?ku ; ic:表記 ?jusho ].
 OPTIONAL{?s rdfs:label ?name .}
}ORDER BY xsd:decimal(?id)

【備考1】
施設の「名称」データが不存在のインスタンスがいくつかある。
(データ作成時の何らかのミスが原因?)
不存在に対応するため、名称を取得する部分はOPTIONAL句を付けている。
OPTIONAL{?s rdfs:label ?name .}

【備考2】
結果を「施設ID」順に並べるのにORDER BYを利用するが、このIDは「桁が揃っていない文字列」なので、数値型に変換してから並べ直す必要がある。
ORDER BY xsd:decimal(?id)
ID番号は、桁数を揃えることが重要だよね。


2 保土ケ谷区の施設を抽出するクエリ

select *
where {
 ?s ic:ID [ic:識別値 ?id ]  ;
    ic:住所 [ ic:区 "保土ケ谷区" ; ic:表記 ?jusho ].
  OPTIONAL{?s rdfs:label ?name .}
}


3 「認可保育所」を抽出するクエリ

select *
where {
 ?s ic:ID [ic:識別値 ?id ]  ;
    ic:住所 [ ic:区 ?ku ;  ic:表記 ?jusho ].
  OPTIONAL{?s rdfs:label ?name .}
  FILTER CONTAINS(STR(?s),"保育所型/10")
}

【考察】
すべての施設は、「保育所型」か「幼稚園型」のいずれかのクラスに属している。
ただし、保育所型クラスに属する施設の、さらに詳細な施設種別は、クラスではなくプロパティで分類されている。よって、詳細な施設種別で分類するのは条件分岐がやや煩雑となる。(そもそもプロパティ設定自体が怪しい施設もあるし…)
そこで着目したのが対象施設のSubject URI(各施設の主語URI。?s のこと)。
施設種別で分類するには、?s に対し、下記の文字列を含むかどうかでフィルタリングするのが一番手っ取り早い。
FILTER CONTAINS(STR(?s),"保育所型/10")

幼稚園型施設   → 「幼稚園型」文字列が含まれる
認可保育所    → 「保育所型/10」文字列が含まれる
幼保連携型認定こども園 → 「保育所型/20」文字列が含まれる
小規模保育事業  → 「保育所型/30」文字列が含まれる
家庭的保育事業  → 「保育所型/40」文字列が含まれる
事業所内保育事業 → 「保育所型/50」文字列が含まれる
横浜保育室    → 「保育所型/60」文字列が含まれる
届出済認可外保育施設 → 「保育所型/70」文字列が含まれる


4 1歳児の入所が可能な施設を抽出

select *
where {
 ?s ic:ID [ic:識別値 ?id ]  ;
    ic:住所 [ ic:区 ?ku ;  ic:表記 ?jusho ];
    dsv:入所可能人数 [ic:種別 "1歳児" ; ic:数値 ?h_kanou_ninzu ].
   OPTIONAL{?s rdfs:label ?name .}
   FILTER (xsd:decimal(?h_kanou_ninzu) > 0 )
}


5 「認定こども園 日野幼稚園」の全情報を取得

PREFIX hoiku:<https://data.city.yokohama.lg.jp/lod/v2/保育所型/> 
PREFIX yochi:<https://data.city.yokohama.lg.jp/lod/v2/幼稚園型/> 
select *
where {
{
 hoiku:202060017 ic:メタデータ / ic:日付 [ic:標準型日付 ?a_joho_koshinbi]  .
}UNION{
 hoiku:202060017 ic:ID [ic:識別値 ?a_id ]  .
}UNION{
 hoiku:202060017 rdfs:label ?a_name  .
}UNION{
 hoiku:202060017 ic:種別コード [ic:種別 ?a_shubetsu ; ic:識別値 ?a_shubetsuData ] .
}UNION{
 hoiku:202060017 ic:住所 ?bn5 .
 ?bn5 ic:区 ?a_ku .
 ?bn5 ic:表記 ?a_jusho .
 OPTIONAL {?bn5 ic:郵便番号 ?a_yuubin.}
}UNION{
 hoiku:202060017 ic:アクセス ?bn5_2 .
 OPTIONAL {?bn5_2 ic:備考 ?h_ikikata .}
}UNION{
 hoiku:202060017 ic:連絡先 ?bn6 .
 OPTIONAL{ ?bn6 ic:電話番号 ?a_tel.}
 OPTIONAL{ ?bn6 ic:FAX番号  ?a_fax.}
 OPTIONAL{ ?bn6 ic:Webサイト ?a_Web.}
}UNION{
 hoiku:202060017 ic:関与 ?bn8 .
 OPTIONAL{ ?bn8 ic:関与者 [ic:名称 [ic:表記 ?a_secchisha]].}
 OPTIONAL{ ?bn8 ic:関与者 [ic:氏名 [ic:表記 ?y_encho]].}
}UNION{
 hoiku:202060017 ic:記述 ?bn10 .
 OPTIONAL{ ?bn10 ic:種別 ?a_sonotaL .}
 OPTIONAL{ ?bn10 ic:説明 ?a_sonotaD .}
}UNION{
hoiku:202060017 ic:利用可能時間 ?bn6_2 .
 OPTIONAL{ ?bn6_2 ic:種別 ?h_syubetsu }
 OPTIONAL{ ?bn6_2 ic:説明 ?h_setsumei }
}UNION{
 hoiku:202060017 ic:建物 ?bn7 .
 OPTIONAL{ ?bn7 ic:建物面積 [ic:数値 ?h_tatemono]. }
 OPTIONAL{ ?bn7 ic:敷地面積 [ic:数値 ?h_shikichi]. }
}UNION{
 hoiku:202060017 dsv:評価 ?bn9 .
 ?bn9 dsv:評価ステータス ?h_dai3sya .
 OPTIONAL{ ?bn9 dsv:結果公表日時 ?h_dai3date .}
 OPTIONAL{ ?bn9 ic:参照 [ic:参照先 ?h_dai3URL ] .}
}UNION{
 hoiku:202060017 ic:収容人数 [ic:種別 ?h_syuyou ; ic:数値 ?h_syuyouninzu ].
}UNION{
 hoiku:202060017 dsv:入所児童数 ?bn12 .
 OPTIONAL{ ?bn12 ic:種別 ?h_nyusyojidou .}
 OPTIONAL{ ?bn12 ic:数値 ?h_nyusyojidou_ninzu .}
 OPTIONAL{ ?bn12 ic:メタデータ / ic:日付 / ic:標準型日付 ?h_nyusyojidou_meta. }
}UNION{
 hoiku:202060017 dsv:入所可能人数 ?bn13 .
 OPTIONAL{ ?bn13  ic:種別 ?h_kanou .}
 OPTIONAL{ ?bn13 ic:数値 ?h_kanou_ninzu .}
 OPTIONAL{ ?bn13 ic:メタデータ / ic:日付 / ic:標準型日付 ?h_kanou_meta. }
}UNION{
 hoiku:202060017 dsv:入所待ち人数 ?bn14 .
 OPTIONAL{ ?bn14  ic:種別 ?h_machi .}
 OPTIONAL{ ?bn14 ic:数値 ?h_machi_ninzu .}
 OPTIONAL{ ?bn14 ic:メタデータ/ ic:日付 / ic:標準型日付 ?h_machi_meta. }
}UNION{
 hoiku:202060017 ic:備考 ?y_bikou .
}UNION{
 hoiku:202060017 ic:料金 [ ic:種別 ?y_ryokins ; ic:説明 ?y_ryokin ].
}UNION{
 hoiku:202060017 dsv:認可年月日 ?y_ninka .
}
}

【雑感】
上記が、ある施設が有している全プロパティの値を表示させるクエリだ。
施設によって、プロパティの存在・不存在がバラバラなので、UNION句とOPTIONAL句の組み合わせを工夫することによりそれに対応させている。

しかしながらいつも思うのが、共通語彙基盤のRDFをグラフ型データベースから抽出するには、このような複雑な呪文を詠唱する必要があるため、利活用のハードルがどーんと上がってしまうのが残念すぎること。呪文が長い分、抽出効率も悪いし。

一方、フラットな構造であるウィキデータから、例えば「横浜市役所」の持つ全部の情報を手に入れるのは、たった1行のクエリでできる。超かんたんで速い。スマート。
SELECT * WHERE { wd:Q11543018 ?p ?o .}

共通語彙基盤のデータ構造はマークアップ的でありXMLとは相性がいいが、それをグラフ構造(トリプル)にするとブランクノードが挟まりすぎて非常に扱いづらくなる。
共通語彙基盤対応データは、グラフデータベースに突っ込んで使うより、XMLを直接パーシングするなり、JSON-LDのままjavascriptで取り込んじゃったりするほうが向いてるのではないかと思えてきた。このあたり、IMI関係者や有識者の意見をじっくり聞いてみたいところ。

なお、横浜市の保育所等LODはURIの参照解決に対応しており、施設のSubject URIにアクセスするとJSON-LD形式で当該施設の全データが取り出せる。
https://data.city.yokohama.lg.jp/lod/v2/保育所型/202060017
(読みたいときはUnicode(UTF16)でデコードしてください。)
エンドポイントに呪文を送るより、素直に参照解決データを使うほうが簡単だったかも、とアプリを完成させてから思うのでありました。うーむ。。。

2019年3月6日水曜日

統計LOD「社会・人口統計体系」について

社会・人口統計体系 とは、別名「統計でみる都道府県・市区町村のすがた」ともいい、あらゆる統計データを収集・加工し、これを地域別に編成し整備したものである。

このデータをLOD化し、統計LODのエンドポイントからAPIでデータを取得できるようしたのは、総務省統計局及び(独)統計センターの大きな成果の一つだ。

この社会・人口統計体系LOD、上手に使いこなせれば非常に便利で、アイディア次第で面白いことがたくさんできる。

あんなこと や こんなこと

しかしながら現在、社会・人口統計体系LODが、世の中に広く普及し多くの人に利用されているとは全く言い難い状況だ。
(私以外にガチで使っている人っています?)

そして今後、統計LODの予算が減らされ、更新が停止してしまったり、事業そのものが無くなってしまうことを私は危惧している。

ということで、本日はもっと多くの方に社会・人口統計体系LODの良さを知っていただくために、便利なクエリをご紹介。

自作の 統計指標の一覧表(CC0)もプレゼント。

1 ある統計データの自治体ランキングクエリ

これは、ある統計指標(この例では2015年の人口総数)について、全国の自治体(この例では都道府県)の状況を調べるためのクエリ。

PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/>
PREFIX g00200502-code:<http://data.e-stat.go.jp/lod/ontology/g00200502/code/>
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#>
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sacs:<http://data.e-stat.go.jp/lod/terms/sacs#>
PREFIX ic:<http://imi.go.jp/ns/core/rdf#>
select  ?pref ?observation
where {
?s  g00200502-dimension:indicator  g00200502-code:indicator-A1101 ;
    cd-dimension:timePeriod  "2015"^^xsd:gYear ;
    sdmx-measure:obsValue  ?observation ;
    sdmx-dimension:refArea  ?areacode .
?areacode  sacs:administrativeClass  sacs:Prefecture ;
           ic:表記  ?pref .
} ORDER BY DESC(?observation)


2 ある自治体の様々な統計データを並べるクエリ

これは、ある自治体(この例では京都府)について、社会・人口統計体系にあるすべての統計データを調べるためのクエリ。

以下のクエリはすべての調査年のデータが吐き出されるため、データ量が多くブラウザが固まる可能性大。
PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/>
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#>
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/>
select ?indicator ?year ?o
where {
?s  sdmx-dimension:refArea  sac:C26000-19700401 ;
    cd-dimension:timePeriod  ?year ;
    g00200502-dimension:indicator  ?indicator ;
    sdmx-measure:obsValue  ?o .


2015年調査のデータのみ取得の場合はこう。これは固まらない。
PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/>
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#>
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/>
select ?indicator ?o
where {
?s  sdmx-dimension:refArea  sac:C26000-19700401 ;
    cd-dimension:timePeriod  "2015"^^xsd:gYear ;
    g00200502-dimension:indicator  ?indicator ;
    sdmx-measure:obsValue  ?o .
} ORDER BY ASC(?indicator)


2013~2015年調査のデータを取得する場合はこうなる。
PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/>
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#>
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/>
select ?indicator ?year ?o
where {
?s  sdmx-dimension:refArea  sac:C26000-19700401 ;
    g00200502-dimension:indicator  ?indicator ;
    sdmx-measure:obsValue  ?o ;
    cd-dimension:timePeriod ?year .
 { ?s  cd-dimension:timePeriod  "2015"^^xsd:gYear .}
 UNION
 { ?s  cd-dimension:timePeriod  "2014"^^xsd:gYear .}
 UNION
 { ?s  cd-dimension:timePeriod  "2013"^^xsd:gYear .}
} ORDER BY ASC(?indicator) DESC(?year)





IODD2019大阪 ウィキデータ・ソン

インターナショナル・オープンデータ・デイ2019(IODD2019)の大阪会場に参加してきた。

今回のテーマは、ウィキデータの編集。

「ウィキペディア本文」を編集したり新規で項目を作成するのは相応の知識が必要だが、今回はウィキデータに地理情報を追加するという、誰でも手軽に実施できる内容。

具体的には、ウィキペディアに掲載されている様々な施設について、そのウィキデータを見て、座標や住所などの地理情報データがないものを、ネットで情報を調べてどんどん追加していくというもの。

私自身はこれまでウィキペディアの編集やウィキデータの利用をしたことがなかったため、当イベントは、自身の見識を深め、新たなアイディアのインスピレーションを得るのに大いに参考になった。

また、当イベントのおまけ成果物として、ウィキデータを使ったWEBアプリを会場でこしらえた。
これ→ https://www.mirko.jp/iodd2019/

ウィキデータはオープンなSPARQL APIで提供されており、またCORS(クロスオリジンリソースシェアリング)にも対応しているため、ブラウザJavascriptのみで手軽に活用アプリが作成できる。

以下、私がよく使うJavascriptのみのお手軽Webアプリのひな形だ。
これをベースとして色々作れちゃうので、ご参考に。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ウィキデータ可視化アプリ</title>
<script>
function execute() { // ボタンクリック時の動作
    var endpoint = 'https://query.wikidata.org/sparql'; //Endpointをセット
    var method = "POST"; //メソッド(POST or GET)
    //都市コードを取得
    var index = document.selectForm.cityselect.selectedIndex;
       var cityCode = document.selectForm.cityselect.options[index].value;
    //クエリ文字列をセット
    var query =  'SELECT DISTINCT ?s ?label ?point ';
        query += 'WHERE{ ?s rdfs:label ?label;';
        query += 'wdt:P131 wd:' + cityCode +';';
        query += 'wdt:P625 ?point.';
        query += 'FILTER(lang(?label)="ja")}';
    sparqlQuery(query,endpoint,method) ; //スパークルクエリ送信
}
function sparqlQuery(queryStr,endpoint,method) { // XMLHttpRequestでクエリ送信
    var querypart = "query=" + encodeURIComponent(queryStr);
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open(method, endpoint, true);
    xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xmlhttp.setRequestHeader("Accept", "application/sparql-results+json");
    xmlhttp.onreadystatechange = function() {
        if(xmlhttp.readyState == 4) {
            if(xmlhttp.status == 200 || xmlhttp.status == 201 ) {
                onSuccessQuery(xmlhttp.responseText);
            } else {
                document.getElementById("results").innerHTML = "エラー" ;
            }
        }
    }
    xmlhttp.send(querypart);
}
function onSuccessQuery(text) { // 結果(JSON文字列)を配列に格納
    var jsonObj = JSON.parse(text);
    var head , rows ;
    if (jsonObj.responseJSON) {
        head = jsonObj.responseJSON.head.vars;
        rows = jsonObj.responseJSON.results.bindings;
    } else {
        if(!(jsonObj.head)){
            document.getElementById("results").innerHTML = "スパークル構文エラー" ;
            return;
        }
        head = jsonObj.head.vars;
        rows = jsonObj.results.bindings;
    }
    if (rows.length === 0) {
        document.getElementById("results").innerHTML = "検索条件の該当データなし" ;
        return;
    }
    makeTable(head, rows);
}
function makeTable(head, rows) { // 配列をテーブルにして出力
    var html = "<table border='1'><tr>";
    for (var i=0; i<head.length; i++) { //ヘッダ部分の書込み
        html += "<th>" + head[i] + "</th>";
    }
    html += "</tr>";
    for (var i=0; i<rows.length; i++) { //内容の書込み
        html += "<tr>";
        for (var j=0; j<head.length; j++) {
            var col = head[j];
            if(rows[i][col] != null){
                html += "<td>" + rows[i][col].value + "</td>";
            }else{
                html += "<td></td>";
            }
        }
        html += "</tr>";
    }
    html += "</table>";
    document.getElementById("results").innerHTML = html;
}
</script>
</head>
<body>
<form name="selectForm">
<select name='cityselect'>
<option value="Q35765">大阪市</option>
<option value="Q193428">堺市</option>
<option value="Q335423">高槻市</option>
<option value="Q270912">枚方市</option>
<option value="Q467479">豊中市</option>
<option value="Q243863">東大阪市</option>
<option value="Q740456">岸和田市</option>
<option value="Q653510">吹田市</option>
<option value="Q502403">茨木市</option>
<option value="Q389633">寝屋川市</option>
</select>
<input type="button" value="クエリ実行" onclick="execute()">
</form>
<div id="results"></div>
</body>
</html> 

2018年11月11日日曜日

LODチャレンジ2018

自作のWebアプリ、 ザ・地域統計パワーバトル が、LODチャレンジ2018において、グランプリにあたる最優秀賞をいただきました。また、併せて日立株式会社様より Inspire the LOD賞を頂きました。

さらに、自作のオントロジー、OWL de ラーメンオントロジー は、オントロノミー合同会社様から オントロジー賞を頂きました。

自作アプリ等の公表の場を与えてくださるだけでも本当にありがたいことで、それに加えて賞までいただけるのは感謝しきれないくらいです。関係者様ありがとうございます。


僕がデータの利活用やプログラミングに取り組み始めたのは3年ほど前で、それ以前はさほど興味を持っていませんでした。

以前は、休日は大好きな魚釣りに行くことが多かったのですが、一日家を空けて魚を釣って楽しんで帰ってくると、家族から白い目で見られることが多く、やむ無く封印することになりました。

冬はスノーボードに行くのを楽しみにしていましたが、行くたびに時間とお金と体力を使い果たしすぎるために、これもやむ無く封印しています。

そこで目を付けたのがオープンデータ利活用です。

1.家にいながらスキマ時間でできるので家族サービスの支障にならない。
2.PCひとつでできるのでお金がかからず、家計にやさしい。
3.社会貢献にもなる。
4.お父さん意外とすごい人だったんだ、って娘に褒められる。

長く続けていけたらなぁ、と思っています。

2018年9月11日火曜日

IMIツール Ver 1.0.0

共通語彙を利活用するためのツールである IMIツール は、先日まで検証版だったが、このたび待望の正式版 Ver 1.0.0 がリリースされた。
さっそく使ってみる。

とりあえず、検証版で作ったDMDを読み込ませる。

む、エラーだ  (-_-)

まあ、仕様が変わったのだろう。しようがない…

オヤジギャグはさておき、気を取り直して、一からDMDを作り直す。
手持ちのエクセル表を読み込ませて、適切なクラス・プロパティをブラウザ上で選んでいく。
応用語彙を、IMI語彙記法を用いて追加する。
この辺りの操作感は検証版と同じだ。

さて、DMDが無事完成し、RDF出力だ。
果たして検証版のダメな点は直っているのだろうか。

1 JSON-LD ⇒ 検証版から据え置き。不具合解消なし。
2 RDF-XML ⇒ 検証版から据え置き。不具合解消なし。
3 Turtle  ⇒ 検証版から据え置き。不具合解消なし。
4 主語の扱い ⇒ 検証版から据え置き。不具合解消なし。

…ぶっちゃけこれでは使い物にならないっす。
技術委員さん、このRDFちゃんとチェックしてます?ほんとにいいのこれで??
経産省やIPAは、共通語彙基盤を本気で流行らせたい気が無いのだろうか… (-_-)

1千万くれたら僕が最高のツールを作ります。
(ウソです)

2018年8月11日土曜日

統計LODのSPARQLクエリ解説(小地域編)

このたび、Webアプリ 「ザ・地域統計パワーバトル」 を公開した。

このアプリは「シビックパワーバトル」に着想を得て開発したものだ。

バトル自体は興味を引くためのジョークだが、地域と地域を比較したり、また日本(全国値)と比較することで、自分の住んでいる地域の特徴を知り、どのような問題点を持つか把握することが可能となる。地域分析ツールという観点で遊んでいただけたら幸いだ。

さて、このアプリは、e-StatのWeb APIからデータを取得し表示させている。
都道府県と市区町村のデータは REST API から、町丁・字の「小地域」のデータは、統計LOD のSPARQL APIから取得している。

都道府県と市区町村のデータは、SPARQL APIからも取得することが可能だが、実行速度のより高速なREST APIを利用している。
小地域データは現在、SPARQL APIからのみの提供だ。

本稿では、統計LODの小地域データを扱うためのSPARQLクエリを紹介する。クエリをコピペして、統計LODのエンドポイントでお試しあれ。


1 平成27年国勢調査の対象自治体の一覧
PREFIX sdmx-dimension: <http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX qb: <http://purl.org/linked-data/cube#>
PREFIX sacs: <http://data.e-stat.go.jp/lod/terms/sacs#>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
select distinct ?code ?pref ?area
where {
?s  sdmx-dimension:refArea  ?code ;
    qb:dataSet  <http://data.e-stat.go.jp/lod/dataset/g00200521/d0003148521>.
?code  sacs:prefectureLabel  ?pref ;
       ic:表記  ?area ;
}order by ?code

このクエリで、平成27年国勢調査の対象自治体の「期間付き標準地域コード」を取得することができる。(重いので乱発しないこと!)


2 京都市左京区に含まれる小地域の一覧
PREFIX smallArea: <http://data.e-stat.go.jp/lod/terms/smallArea/>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX sac: <http://data.e-stat.go.jp/lod/sac/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
select *
where {
?s  a  smallArea:SmallAreaCode ;
    dcterms:isPartOf  sac:C26103-19700401 ;
    rdfs:label  ?name .
} order by ?s

上記の sac:C26103-19700401 は平成27年国勢調査時点の、京都市左京区の期間付き標準地域コードである。この部分を変えることで他の市区町村の情報を得ることができる。


3 京都市左京区聖護院山王町の情報
PREFIX sa-g00200521-2015: <http://data.e-stat.go.jp/lod/smallArea/g00200521/2015/>
select *
where {
  sa-g00200521-2015:S26103018006 ?p ?o .
}


4 京都市左京区聖護院山王町のポリゴン
PREFIX sa-g00200521-2015: <http://data.e-stat.go.jp/lod/smallArea/g00200521/2015/>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
select ?polygon
where {
  sa-g00200521-2015:S26103018006  geo:hasGeometry  [ geo:asWKT  ?polygon ] .
}
小地域のポリゴンを取得することもできる。当アプリではこのクエリは使っていないが、オープンストリートマップなどで領域を表示させるのも面白いと思う。


5 京都市左京区聖護院山王町の年齢別・男女別人口
PREFIX sdmx-dimension: <http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sa-g00200521-2015: <http://data.e-stat.go.jp/lod/smallArea/g00200521/2015/>
PREFIX qb: <http://purl.org/linked-data/cube#>
PREFIX cd-dimension: <http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX estat-measure: <http://data.e-stat.go.jp/lod/ontology/measure/>
select ?age ?sex ?population
where {
?s  sdmx-dimension:refArea  sa-g00200521-2015:S26103018006 ;
    qb:dataSet  <http://data.e-stat.go.jp/lod/dataset/g00200521/ds012015003>;
    cd-dimension:age  ?age;
    cd-dimension:sex  ?sex;
    estat-measure:population  ?population.
}order by ?sex ?age

このクエリで、いわゆる「人口ピラミッド」に必要な情報を得ることができる。


6 京都市左京区聖護院山王町の年齢別・男女別人口、外国人人口、世帯数、配偶関係、労働力状態
PREFIX sdmx-dimension: <http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sa-g00200521-2015: <http://data.e-stat.go.jp/lod/smallArea/g00200521/2015/>
PREFIX qb: <http://purl.org/linked-data/cube#>
PREFIX cd-dimension: <http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX estat-measure: <http://data.e-stat.go.jp/lod/ontology/measure/>
PREFIX g00200521-dimension-2015: <http://data.e-stat.go.jp/lod/ontology/g00200521/dimension/2015/>
PREFIX g00200521-dimension-2010: <http://data.e-stat.go.jp/lod/ontology/g00200521/dimension/2010/>
select ?dataset ?sex ?age ?maritalStatus ?labourForce ?population ?households ?avrage
where {
?s  sdmx-dimension:refArea  sa-g00200521-2015:S26103018006 ;
    qb:dataSet  ?dataset .
OPTIONAL{
?s  cd-dimension:age  ?age;
    cd-dimension:sex  ?sex;
    estat-measure:population  ?population.
}
OPTIONAL{
?s  estat-measure:households  ?households .
}
OPTIONAL{
?s  cd-dimension:sex  ?sex;
    estat-measure:population  ?population.
}
OPTIONAL{
?s  cd-dimension:sex  ?sex;
    estat-measure:age  ?avrage.
}
OPTIONAL{
?s  cd-dimension:sex  ?sex;
    g00200521-dimension-2015:maritalStatus  ?maritalStatus;
    estat-measure:population  ?population.
}
OPTIONAL{
?s  cd-dimension:sex  ?sex;
    g00200521-dimension-2010:labourForce  ?labourForce;
    estat-measure:population  ?population.
}
}order by ?dataset ?sex ?age ?maritalStatus ?labourForce

「ザ・地域統計パワーバトル」では、実際にこのクエリを使ってデータを取得している。
OPTIONAL句で各種データを並列させているところがポイント。
短いクエリを連発するのが良いのか、このような重いクエリにするのが良いのか悩んだが、プログラムが複雑化するのを避けるために、今回は重いクエリ一発で、情報を一気に取得する方法を採用した。

統計LODの実行速度が倍くらいになってくれたらなぁ…。
統計局さん、統計センターさん、日立さん、よろしくお願いします。

統計LODの実行速度が5倍くらい高速になりました!!
おかげさまで快適になりました。
関係者のみなさま、ありがとうございます!!!