苗字の話(3/3) 苗字の数は減っていく?(特殊出生率=2.00)

子どもが体調を崩したり、実家に帰ったり、子どもが体調を崩したり、子どもが体調を崩したり、で全然PCを触れておりませんでした。ようやく続きでございます。

前回、苗字の数がかなりのペースで減っていく様子をみました。が、人口減少の環境下では苗字の数が減るのは当然のこと。再生産率が7割しかないということは、2世代で人口が半減してしまうわけで。
次は、条件を変えてシミュレーションしてみます。再生産率=1.0、つまり人口の増減が(乱数の要素を除けばほぼ)無い社会ではどうなるのか。再生産率だけ変えて、シミュレーションしなおしてみます。

人口減がないのでシミュレーション時間がえらい長くなりましたよ……


こちらがシミュレーションの結果です(計算結果はこちら)。青が人口、赤が苗字の数です。横軸は世代数です。
人口維持の環境下では、100世代後(3000年後?)でも4万個程度の苗字が維持できるようです。
対数軸で表したものも掲載しましょう。

苗字の数は概ね直線ぽいですが、序盤のカーブが急。もしかして……と両対数で表示すると、

すごく……直線です……(相関係数0.994)。人口維持の環境下では、苗字の数と世代数は冪乗則の関係にあるようです。なんでだろう……
これは定性的には何を表しているんでしょう。よくわかりません。順位と頻度の間には冪乗則が成立することが多いという経験則は、まぁ理解できなくはないのですが、世代数と頻度の間に冪乗則が成立するというのは理解に苦しみます。遺伝的多様性の研究なんかにヒントがあるんかなぁとか想像しますが。

次に、上位10個、100個、1000個、10000個、100000個の苗字が占めるシェアを見てみましょう。

殆ど減っていません。数世代の後に10万位以下の苗字は全滅してしまうのですが、上位一万位の苗字のシェアは100世代経過してもほとんど変化しません。


今回苗字の数の変化を検討してみようと思ったのは、現在のような命名システムを使い続けていくと苗字の数が激減し、中国や韓国みたいに数少ない苗字に集約されてしまうのではないか?と考えたからです。
シミュレーションをしているうちに、苗字の減少といっても分けて二つの現象が現われるということがわかってきました。
1. レア苗字が絶滅する。
2. 有力苗字による寡占が生ずる。
中韓化は2.の方の現象ですね。

人口減少の環境下では苗字の数は指数関数的に減少し、レア苗字の絶滅、有力苗字による寡占の両方が生じましたが、その要因は人口減少が主でした。
人口維持の環境下では苗字の数の減少はかなり緩やかで、100世代後でも4万個もの苗字を維持することができている上、有力名字による寡占は生じませんでした。よって、苗字の保全という観点からは、現在の命名システムにはそんなに問題はなさそうだと言えます。もちろん、人口維持の環境下でも今から2世代後に苗字が24万種から17.5万種に減少してしまうことを「問題ない」といっていいかという話はありましょうが。

人口減少が苗字の数に非常に大きな影響を与える、特に人口が一定値を切ると苗字の数の維持が急に困難になるという性質を考えると、戦乱などにより人口が急減するような状況が苗字の数の維持に対する一番大きな障害になっていそうだ、と言えましょう。
中国や韓国で苗字の数が少ないのは、歴史的に古くから苗字を名乗っており、かつ時折人口が激減したからなのかな?などと想像します。昔はもっとたくさんの苗字が存在していたのかも。
一方で日本は、現在のかっちりしたシステムに基づいて苗字を名乗り始めた歴史が浅く、その後大きな人口減にも遭遇していないため、バリエーションに富んだ苗字の数々を維持できているのかもしれません。

個人的にはレア苗字は好きなので、創姓、つまり新たな姓を名乗ることができるようなシステムも悪くないのではないかと思っているんですけどね。保井春海が後に渋川春海を名乗ったように。むちゃくちゃな創姓を抑止したいならば、家系をさかのぼって祖先の誰かが名乗っていた苗字であれば名乗れるようにするとか。
不祥事を起こした企業が企業名を変えるような感じで、悪いことするたびに名前を変える人が出てきそうな気もしますね。やっぱダメか。

苗字の話(2/3) 苗字の数は減っていく?(特殊出生率=1.37の場合)

結婚すると苗字はどちらかのものに統一されるため、苗字の数は減少する一方です。
では、その減り具合はどのようなものになるのでしょうか。以下のモデルで検証してみました。

  • 男女の数は等しいとし、全ての男女は同時に結婚して子をなすものとします。
  • 再生産率をrrとすると(rrを2倍すると特殊出生率になる。rr = 1で人口維持)、
    • 1. rr / (1 + rr) の確率で一人子供を産む。産まなかった場合は終了。
    • 2. 産んだ場合は、再度1の処理を行う。
    • 生まれる子供の数の期待値はrrになります。
  • 子は親のどちらかの苗字を継承します。

苗字の世帯数の分布は、苗字館のデータと、前回計算した推計値を使います。総人口は2005年の統計値である127768000人とします。このときの特殊出生率は1.37だったそうなので、再生産率は1.37/2とします。
↓こちらがシミュレーションの結果です(計算結果はこちら)。青が人口、赤が苗字の数です。横軸は世代数です。等比数列的に両方とも減少してます。

↓こちらが対数軸のものです。

人口の再生産率が7割を切っているので、2世代ごとに人口が半分になっていって、48世代目が一人も子を残せなかったためそこで日本人が滅んでいます。一世代が約30年として、36世紀頃に滅ぶ計算ですな。
一方で苗字の数の減り方は人口の減り方よりは緩やかなようです(特に序盤)。30世代後(今から1000年後くらい)から苗字の減り方が急峻になるのが見て取れます。人口がある一定数を切ると苗字の数の維持が急に困難になるのかもしれません。
それにしても、2世代後の時点で既に苗字が24万個から15万個に激減しているのが興味深いところです。レア苗字の保全は急務かもしれませぬ。

次に、上位10個、100個、1000個、10000個、100000個の苗字が占めるシェアを見てみましょう。↓

指数関数でもべき乗則でもない変化が見られます。どの線を見ても「しばらくの間維持」->「急激にシェア減退」->「全滅」という経緯をたどっています。ここでも、人口が一定値を切ると苗字の数の維持が急に困難になる可能性が示されているように思えます。

この結果からなにがわかるか。
我々が生きている間(今から+2世代くらい)に、苗字の数は2/3程度に減少するでしょう。
しかし、上位10, 100, 1000, 10000個の苗字が占めるシェアはそれぞれ現在と変わりないため、苗字の数が減少したことを体感することはないはずです。
レア苗字はひっそりと消えていきます。

なお、シミュレーションに用いたコードとデータはここに置いています(Python3系で実行)。計算に時間がかかるので並行処理化したのですが、スレッド数を増やしても全くスループットが上がらないことに驚愕するものの、Pythonだし仕方ないか。

苗字の話(1/3) 日本にはいったいいくつの苗字が存在するのか

苗字舘には苗字ランキング30000位までの世帯数が掲載されています。
順位と世帯数をグラフに書いてみると、以下のようになります。

うん。急峻すぎてよくわかりませんね。

両対数グラフにすると、以下の通り。


直線ぽくなっていますね。世帯数と順位の関係は、冪乗則に沿っているようです。今流行の冪乗則! なんで苗字の分布が冪乗則になるんだろうか……それはさておき。
世帯数をf, 順位をrとすると、
log f = -1.45 log r + 8.00
という回帰直線が書けるようです(logは常用対数)。相関係数は0.986で、相当よろしいようです。が、上掲のグラフを見て解るとおり分布はきれいな直線になっているわけではなく、特に上位の苗字は回帰直線をかなり下回っています。下位の苗字は冪乗則だけど、上位の苗字はそうではないルールで分布しているように見えます。

さて、日本にはいったいいくつの苗字が存在するのでしょうか? 苗字舘に掲載されていない30000位未満の苗字の世帯数を概算したいのですが、上掲の回帰直線は概算に用いるには誤差が大きすぎるように感じています。
そこで、20000位から30000位までの苗字で回帰直線を書くと、
log f = -1.81 log r + 9.52
となり、相関係数は0.998と改善します。
この式を使って30000位未満の苗字の世帯数を計算していくと、1位から∞位までの苗字の世帯数の総和は29,931,171世帯に近づいていきます。2005年の調査によると日本には49,063,000世帯存在するようなので、苗字舘に掲載の世帯数、および上掲の回帰直線で推測される世帯数を約5/3倍すれば、実際の世帯数に近い値となるはずです。
上掲の回帰曲線を用いて推測される世帯数に約5/3の係数をかけると、240,231位の苗字までが世帯数1以上となり、つまり日本には苗字が240,231個あるだろうと推測出来ます。有識者による見解では日本の苗字は15万から30万個と言われているようなので、それなりに良い数字が出ているように見えます。

大人の夏休み

お久しぶりでございます。8月いっぱいで現在の会社を退職し、9月より新しい会社に勤務の予定なのです。

そして現在、夢の有給休暇消費モードなのですよ! 大人の夏休み! 時期的にもちょうど夏休みだし!せっかくのお休みなので、夏休みの自由課題とか色々やってみたいと思っております。

とりあえず今日は家事と子守で消耗し尽くしました……

図書館蔵書検索サイトcalil.jp

カーリル | 日本最大の図書館蔵書検索サイト

「全国4300以上の図書館から検索できます」
ですって!

APIを公開予定だとのことなので、利用許諾を申請しました。使えるようになったらベータテストに参加させていただけるかもしれないそうです。そうなったら、bibliwoの対応図書館がものすごく増えます。bibliwoの現在の対応図書館は181件なので、その差は圧倒的(とはいえ、4300ってのは図書館分室も含めているような気もしなくはない。例えば、墨田区なら8件の本館/図書館があるのだけど、これを8個として数えている可能性がある)。

将来的にはcalil.jp謹製のブラウザ拡張がリリースされることは間違いないと思っているので、それまでのつなぎとして役割を果たせればいいな、というのが現時点の将来展望です。


ちなみに、各都道府県の横断検索サイトならhttp://www.jla.or.jp/link/public2.htmlにまとまっているので、これでかなりの人は用が足せるかもしれません。

GreasemonkeyのUserscriptをJetpackとChromeExtensionに移植する際のメモ

先日BibliwoJetpackとChromeExtensionに移植したのでその際のメモです。
GM版とこれら二つを個別に保守するのは御免こうむりたかったため、なるべくシングルソースにしようという方針に沿っています(Chrome版は諸般の事情によりコードを分けてますが)。

GM版を移植する際に検討すべき項目は、主に以下のものがあります。

Jetpackへの移植

ソースコードこちらGM版と全く同じです。

URLに基づいてページ書き換え用のスクリプトを実行するか否か判定する方法

GMではコード先頭にコメントとして
// @include http://*.amazon.co.jp/*
のようにコードをインジェクトするルールが定義されていますが、これをJetpackでも使いたい。
これは、Greasemoneky拡張のコードをそのまま引っ張ってきました(convert2RegExpのあたり参照)。これでルールの非互換性とかを気にせずに済むので。ただし、コードがそれなりにでかくなってしまいます。

ページ書き換え用のスクリプトを実行する方法

Jetpackチュートリアルあたりにも記述があったような気がしますが、jetpack.tabs.onReady()を使ってタブ内のドキュメントが準備できたときに呼ばれるコールバックを登録します。コールバック引数でdocumentがえられるので、それをスクリプト本体に渡して実行します。
問題はdocument以外の変数を必要とする場合でして、どうにかして取得する必要があります。例えば、window変数は以下のようなコードで取得しました。

jetpack.tabs.onReady(function(doc){
   var i, j;
   var unsafeWindow = null;
   var tmpwin;
   function innerWindows(win){
      var ret = [];
      var i;
      ret.push(win);
      for(i = 0; i < win.frames.length; ++i){
         ret.push(win.frames[i]);
      }
      return ret;
   }
    function test(page) {
      return convert2RegExp(page).test(doc.location);
    }
   if(!includes.some(test) || excludes.some(test)){
      return;
   }
   for(i = 0; i < jetpack.tabs.length; ++i){
      tmpwin = innerWindows(jetpack.tabs[i].contentWindow);
      for(j = 0; j < tmpwin.length; ++j){
         if(tmpwin[j].document === doc){
            unsafeWindow = tmpwin[j];
            break;
         }
      }
      if(unsafeWindow){
         break;
      }
   }
   scriptmain(doc);
});

ドキュメントが開かれたら、そのURLが処理対象であるか否か判定。その後、全タブの全windowを検索して、そのdocumentが属しているwindowオブジェクトを取得しています(が使っていません。使わないように処理本体のコードを変更しました)。Jetpack側でwindow変数を渡して欲しいような気もします。
この辺を真面目にやろうとすると、サンドボックスっぽく作らないとダメだと思います。ここで取得したwindowsはunsafeWindowなので、安全なwindowを作ってあげないといけない。

GM_logの移植
GM_log = console.log;

グローバル変数を惜しみなく使いました。

GM_xmlhttpRequest

Jetpack環境下ではクロスオリジンのXHRが使えるので、Greasemoneky拡張で定義されているGM_xmlhttpRequestのコードを必殺コピペで大体動きました。

   GM_xmlhttpRequest = function(details){
     var req;
     var url = details.url;
      function getCallback(event){
         return function(){
            if(details[event]){
               var responseState = {
                 responseText: req.responseText,
                 readyState: req.readyState,
                 responseHeaders: null,
                 status: null,
                 statusText: null,
                 finalUrl: null
               };
               if (4 == req.readyState && 'onerror' != event) {
                  responseState.responseHeaders = req.getAllResponseHeaders();
                  responseState.status = req.status;
                  responseState.statusText = req.statusText;
                  responseState.finalUrl = req.channel.URI.spec;
               }
               details[event](responseState);
            }
         }
      }
     
     if (typeof url != "string") {
       throw new Error("Invalid url: url must be of type string");
     }
     if(!url.match(/(.*?):/)[1]) {
         throw new Error("Invalid url: " + url);
     }
     req = new XMLHttpRequest();
     req.onload = getCallback("onload");
     req.onerror = getCallback("onerror");
     req.onreadystatechange = getCallback("onreadystatechange");

     req.open(details.method, url);

     if (details.overrideMimeType) {
       req.overrideMimeType(details.overrideMimeType);
     }

     if (details.headers) {
       for (var prop in details.headers) {
         req.setRequestHeader(prop, details.headers[prop]);
       }
     }
     var body = details.data ? details.data : null;
     if (details.binary) {
       // xhr supports binary?
       if (!req.sendAsBinary) {
         var err = new Error("Unavailable feature: " +
                 "This version of Firefox does not support sending binary data " +
                 "(you should consider upgrading to version 3 or newer.)");
         throw err;
       }
       req.sendAsBinary(body);
     } else {
       req.send(body);
     }
   };
GM_set/getValue
jetpack.future.import("storage.simple");
GM_getValue = function(key, def){
   var ret = jetpack.storage.simple[key];
   if(ret === undefined){
      ret = def;
   }
   return ret;
};
GM_setValue = function(key, value){
   jetpack.storage.simple[key] = value;
};

storage.simple万歳。

Jetpack化まとめ

基本的にはこんだけです。GM -> Jetpackは簡単! いや本当はGM_registerMenuCommandも移植した方がいいんすけどね、めんどくさくて……。
GM -> Jetpackの変換は自動化できると思います。どなたか作ってみてはいかがでしょう。

ChromeExtensionへの移植

UserscriptってChromeでそのまま動くようになったんでしょ?という方もおられましょうが、実はGM_xmlhttpRequestGM_get/setValueも使えないので、相当数のUserscriptは動かないんです。

主なソースコードこちらGM版、Jetpack版と全く同じです。
マニフェスト
バックグラウンド

今回はcontent_scriptsの仕組みを使いました。特定のURLをもつドキュメントに対し、JavaScriptのコードをインジェクトできる仕組みで、これだけ聞くとGreasemonkeyそのものじゃんと思わなくはないんですが割とその通りの仕組みです。でも制約が結構厳しい。

URLに基づいてページ書き換え用のスクリプトを実行するか否か判定する方法 + ページ書き換え用のスクリプトを実行する方法

マニフェストにマッチパターンを書いて、content_scriptとしてメインのスクリプトを指定しましょう。

"content_scripts": [
  {
    "matches": [
      "http://*.amazon.co.jp/*",
      "http://amazon.co.jp/*",
      "http://www.bk1.jp/*",
      "http://books.rakuten.co.jp/*",
      "http://www.bookoffonline.co.jp/display/*",
      "http://store.shopping.yahoo.co.jp/7andy/*",
      "http://www.jbook.co.jp/p/p.aspx/*"
    ],
    "js": ["bibliwo.user.js"]
  }
],

ただし、マッチの書式はGreasemonkeyのそれとは非互換です。例えば、ホスト名に使える*(半角アスタリスク)は先頭に1つだけです(GMは複数使える)。
今回は、GM版のマッチパターンの方をChrome式に変更しました。大抵、Chrome式マッチパターンで書けば、GM版でも使えるみたいです。

GM_logの移植
GM_log = console.log;
GM_xmlhttpRequest

content_script内で使えるXHRオブジェクトはクロスオリジン可能なXHRではありませんし、殆どのAPIも使えません。

However, content scripts have some limitations. They cannot:
* Use chrome.* APIs (except for parts of chrome.extension)
* Use variables or functions defined by their extension's pages
* Use variables or functions defined by web pages or by other content scripts
* Make cross-site XMLHttpRequests

http://code.google.com/chrome/extensions/content_scripts.html

バックグラウンドページでクロスオリジンXHRオブジェクトを用意して、chrome.extension.getBackgroundPage()でそのオブジェクトにアクセスするなんて手段も使えません(getBackgroundPage()が許可されないため)。
バックグラウンドページでページ読み込みを監視し,読み込まれたらchrome.tabs.executeScript()を使ってそのページに対しクロスオリジンXHRを使用したいスクリプトを実行する方法も試したのですが、executeScript()で実行されるスクリプトはcontent_scriptと同等の制約がかかるらしく(ドキュメント未記述のため不正確かも)、この方法もダメでした。

結局、バックグラウンドページ側でGM_XHR相当の処理を作っておき(バックグラウンド内ではクロスオリジンXHRが使える)、ページ間通信で処理を依頼する方法で実装しました。

XHRを使う側はこちらのコード。簡単ですね。

GM_xmlhttpRequest = function(detail){
   chrome.extension.sendRequest(detail, function(response) {
      if(detail[response.event]){
         detail[response.event](response.res);
      }
   });
};

バックグラウンド側は、

function GM_xmlhttpRequest(details){
 (中略; 殆どJetpack版と同じ)
}

chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
    request.onload = function(res){
      sendResponse({event : "onload", res : res});
    };
    request.onerror = function(res){
      sendResponse({event : "onerror", res : res});
    };
    request.onreadystatechange = function(res){
      //sendResponseするとコールバックが解除されるため、あえて呼ばない。
      //onreadystatechangeは使っていないので、この実装でとりあえずOK
      //sendResponse({event : "onreadystatechange", res : res});
    };
    GM_xmlhttpRequest(request);
});

こんな感じの実装です。
onreadystatechangeを殺しています。sendResponseを実行するとsendRequest/Response間の接続が切れてしまうそうなので、通信結果が確定したその一回だけsendResponseするようにしました。途中経過も送る必要があるときは、postMessageを使うべきでしょう(参照)。

GM_set/getValue

実は移植できませんでした……。

GM_getValue = function(key, def){
   var ret = localStorage[key];
   if(ret === undefined){
      ret = def;
   }
   return ret;
};
GM_setValue = function(key, value){
   localStorage[key] = value;
};

素のlocastorageを使っているので、ストレージの実体がドメインごとに存在してしまいます。Bibliwoの場合はあんまり不便じゃなかったのでそのまま出してしまいましたが、たいていの場合はダメだと思います。誰か解決策考えてください。

content_script内では殆どのAPIが使えないので、組込みオブジェクトかバックグラウンドページを使うしか実装できないと思うのですが。僕の知ってる限りでは、GM_get/setValueの振る舞いを実現できる組込みオブジェクトは無いですし、XHRと違ってGM_get/setValueは同期関数なのでメッセージングの仕組みを使って実装することもできません(バックグラウンド側とcontent_scriptは違うプロセスで実行されるんじゃないかと思うので、同期でバックグラウンド側と通信するってのはそもそも無理な話だと予想)。

どうにかならないですかねぇ。
というか今回苦労したクロスオリジンXHRとget/setValueって、Chrome上でGreasemonkeyのUserscriptを動かすときの制約そのものなんですよね。割と簡単な実装でGreasemonkey対応をしてるんじゃないかなあと想像した次第。

スクリプト本体でGM_get/setValueを使うのをやめて、非同期の独自APIを使うようにすれば解決します。

//GM版
MY_getData = function(callback, key, dflt){
  callback(GM_getValue(key, dflt));
};
//Chrome版
MY_getData = function(callback, key, dflt){
   chrome.extension.sendRequest({key, dflt}, function(response) {
       callback(response);
   });
};

こんな感じで。でも、なんか、うーん。Chrome側でAPI用意して欲しいなあ。

ChromeExtension化まとめ

マニフェストファイルが必要なのは仕方ないとして、XHRのためにバックグラウンドページも必要になりました。シングルソースへの道は遠い。
しかもGM_get/setValueにはきれいな解決策も見つからないままで、なんかもやもやした感じが晴れません。

総まとめ

ChromeGM_get/setValue問題は残りつつも、Jetpack化、ChromeExtension化はそんなに手間がかかりませんでした。GreasemonkeyAPIはごく少ない上にシンプルなものなんですが、これだけ多くの問題を解決できてるということはAPI設計がそれだけ優れていたということですね。見習わねば。

Bibliwoにて茨城県立と、埼玉県下の大多数の図書館に対応

Bibliwo

折角移植したのでChromeExtension版を暫く使っているのですが、快適だ……。テストスクリプト登録機能も移植しちゃって、Chromeに完全移行しちゃおうかと思案している今日この頃です。