{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 地図作成のビジュアライゼーション\n", "\n", "_「地図作成は、人類の最も長い歴史を持つ知的活動の一つであり、また最も複雑な活動の一つでもあります。科学的理論、グラフィカルな表現、地理的事実、実用的な考慮が絶え間ない形で融合しています。」_ — [H. J. スチュワード](https://books.google.com/books?id=cVy1Ms43fFYC)\n", "\n", "地図作成(カートグラフィー)は、発見とデザインの何世紀にもわたる歴史を持つ分野です。地図作成ビジュアライゼーションは、位置、ルート、地球表面上の軌跡など、空間情報を含むデータを伝えるために地図作成技術を活用します。\n", "\n", "<div style=\"float: right; margin-left: 1em; margin-top: 1em;\"><img width=\"300px\" src=\"https://gist.githubusercontent.com/jheer/c90d582ef5322582cf4960ec7689f6f6/raw/8dc92382a837ccc34c076f4ce7dd864e7893324a/latlon.png\" /></div>\n", "\n", "地球を球体として近似する場合、位置は_緯度_(赤道から北または南に何度かの角度)と_経度_(東西の位置を指定する角度)の球面座標系を使用して表すことができます。このシステムでは、_平行線_は一定の緯度を持つ円であり、_子午線_は一定の経度を持つ円です。[本初子午線](https://en.wikipedia.org/wiki/Prime_meridian)は0°経度に位置し、慣例としてイギリス・グリニッジの王立天文台を通過するものとして定義されています。\n", "\n", "三次元の球体を二次元の平面に「平坦化」するためには、[投影法](https://en.wikipedia.org/wiki/Map_projection)を適用する必要があります。これにより、(経度、緯度)のペアを(x、y)の座標にマッピングします。[スケール](https://github.com/uwdata/visualization-curriculum/blob/master/altair_scales_axes_legends.ipynb)と似たように、投影法はデータのドメイン(空間的な位置)から視覚的なレンジ(ピクセル位置)にマッピングします。ただし、これまで見てきたスケールは一方向のドメインを受け入れていましたが、地図投影は本質的に二方向のものです。\n", "\n", "このノートブックでは、Altairを使って地図を作成し、空間データを視覚化する基本を紹介します。内容は以下の通りです:\n", "\n", "- 地理的特徴を表現するためのデータ形式、\n", "- ポイント、シンボル、コロプレス地図などの地理的視覚化技法、\n", "- 一般的な地図投影法のレビュー。\n", "\n", "_このノートブックは[データ視覚化カリキュラム](https://github.com/uwdata/visualization-curriculum)の一部です。_" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import altair as alt\n", "from vega_datasets import data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 地理データ:GeoJSON と TopoJSON" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "これまで、JSONやCSV形式のデータセットを使用して、行(レコード)と列(フィールド)からなるデータテーブルを操作してきました。地理的な地域(国、州、_など_)や軌跡(飛行ルート、地下鉄路線、_など_)を表現するには、豊富な幾何学的情報をサポートする追加の形式を導入する必要があります。\n", "\n", "[GeoJSON](https://en.wikipedia.org/wiki/GeoJSON)は、特別なJSON形式内で地理的特徴をモデル化します。GeoJSONの`feature`は、国境を構成する`経度`、`緯度`座標などの幾何学的データと、その他のデータ属性を含むことができます。\n", "\n", "以下は、アメリカ合衆国コロラド州の境界を示すGeoJSONの`feature`オブジェクトです:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "~~~ json\n", "{\n", " \"type\": \"Feature\",\n", " \"id\": 8,\n", " \"properties\": {\"name\": \"Colorado\"},\n", " \"geometry\": {\n", " \"type\": \"Polygon\",\n", " \"coordinates\": [\n", " [[-106.32056285448942,40.998675790862656],[-106.19134826714341,40.99813863734313],[-105.27607827344248,40.99813863734313],[-104.9422739227986,40.99813863734313],[-104.05212898774828,41.00136155846029],[-103.57475287338661,41.00189871197981],[-103.38093099236758,41.00189871197981],[-102.65589358559272,41.00189871197981],[-102.62000064466328,41.00189871197981],[-102.052892177978,41.00189871197981],[-102.052892177978,40.74889940428302],[-102.052892177978,40.69733266640851],[-102.052892177978,40.44003613055551],[-102.052892177978,40.3492571857556],[-102.052892177978,40.00333031918079],[-102.04930288388505,39.57414465707943],[-102.04930288388505,39.56823596836465],[-102.0457135897921,39.1331416175485],[-102.0457135897921,39.0466599009048],[-102.0457135897921,38.69751011321283],[-102.0457135897921,38.61478847120581],[-102.0457135897921,38.268861604631],[-102.0457135897921,38.262415762396685],[-102.04212429569915,37.738153927339205],[-102.04212429569915,37.64415206142214],[-102.04212429569915,37.38900413964724],[-102.04212429569915,36.99365914927603],[-103.00046581851544,37.00010499151034],[-103.08660887674611,37.00010499151034],[-104.00905745863294,36.99580776335414],[-105.15404227428235,36.995270609834606],[-105.2222388620483,36.995270609834606],[-105.7175614468747,36.99580776335414],[-106.00829426840322,36.995270609834606],[-106.47490250048605,36.99365914927603],[-107.4224761410235,37.00010499151034],[-107.48349414060355,37.00010499151034],[-108.38081766383978,36.99903068447129],[-109.04483707103458,36.99903068447129],[-109.04483707103458,37.484617466122884],[-109.04124777694163,37.88049961001363],[-109.04124777694163,38.15283644441336],[-109.05919424740635,38.49983761802722],[-109.05201565922046,39.36680339854235],[-109.05201565922046,39.49786885730673],[-109.05201565922046,39.66062637372313],[-109.05201565922046,40.22248895514744],[-109.05201565922046,40.653823231326896],[-109.05201565922046,41.000287251421234],[-107.91779872584989,41.00189871197981],[-107.3183866123281,41.00297301901887],[-106.85895696843116,41.00189871197981],[-106.32056285448942,40.998675790862656]]\n", " ]\n", " }\n", "}\n", "~~~" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`feature`には`properties`オブジェクトが含まれており、ここには任意の数のデータフィールドが含まれます。さらに`geometry`オブジェクトがあり、今回は州の境界を示す`[経度, 緯度]`の座標からなる単一のポリゴンが含まれています。座標は右方向に少し進みますので、必要に応じてスクロールできます。\n", "\n", "GeoJSONの詳細について学ぶには、[公式GeoJSON仕様書](http://geojson.org/)をご覧いただくか、[Tom MacWrightによる役立つ入門書](https://macwright.org/2015/03/23/geojson-second-bite)をお読みください。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "GeoJSONのストレージ形式の欠点の一つは、冗長性があり、結果としてファイルサイズが大きくなることです。例えば、コロラド州は6つの他の州(アリゾナ州と接する角を含めると7つ)と境界を共有しています。それぞれの州について重複する座標リストを使用するのではなく、共有する境界を一度だけエンコードするという、よりコンパクトなアプローチが可能です。これにより、地理的な地域の_トポロジー_が表現されます。幸い、これこそが[TopoJSON](https://github.com/topojson/topojson/blob/master/README.md)フォーマットが行うことです!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "世界の国々を110メートル解像度で示すTopoJSONファイルをロードしましょう:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://vega.github.io/vega-datasets/data/world-110m.json'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "world = data.world_110m.url\n", "world" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "world_topo = data.world_110m()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_keys(['type', 'transform', 'objects', 'arcs'])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "world_topo.keys()" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Topology'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "world_topo['type']" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_keys(['land', 'countries'])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "world_topo['objects'].keys()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_上記の`world_topo` TopoJSON辞書オブジェクトを調べて、その内容を確認します。_\n", "\n", "このデータでは、`objects`プロパティがデータから抽出できる名前付き要素を示しています:すべての`countries`のジオメトリ、または地球上のすべての`land`を表す単一のポリゴンです。これらのいずれかをGeoJSONデータに展開して視覚化することができます。\n", "\n", "TopoJSONは専門的なフォーマットであるため、AltairにTopoJSONフォーマットを解析するように指示する必要があります。これにより、トポロジーから抽出したい名前付きの特徴オブジェクトを指定します。以下のコードは、`world`データセットから`countries`オブジェクトのGeoJSON特徴を抽出したいことを示しています:\n", "\n", "~~~ js\n", "alt.topo_feature(world, 'countries')\n", "~~~\n", "\n", "この`alt.topo_feature`メソッド呼び出しは、次のVega-Lite JSONに展開されます:\n", "\n", "~~~ json\n", "{\n", " \"values\": world,\n", " \"format\": {\"type\": \"topojson\", \"feature\": \"countries\"}\n", "}\n", "~~~\n", "\n", "これで地理データをロードできるようになったので、地図作成を始めましょう!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Geoshape Marks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "地理データを視覚化するために、Altairは`geoshape`マークタイプを提供します。基本的な地図を作成するには、`geoshape`マークを作成し、TopoJSONデータを渡します。このデータは、各国のGeoJSON特徴に展開されます:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-ded4d19b935a4b7187f34bfa41e0981a\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-ded4d19b935a4b7187f34bfa41e0981a\") {\n", " outputDiv = document.getElementById(\"altair-viz-ded4d19b935a4b7187f34bfa41e0981a\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": \"geoshape\", \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(alt.topo_feature(world, 'countries')).mark_geoshape()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "上記の例では、Altairはデフォルトで青色を適用し、デフォルトの地図投影法(`mercator`)を使用しています。標準のマークプロパティを使用して、色や境界の線幅をカスタマイズすることができます。`project`メソッドを使用することで、独自の地図投影法を追加することも可能です。\n", "\n", "例えば、以下のコードでは、`project`メソッドを使って`albersUsa`投影法を適用し、国の境界線を太くし、色をカスタマイズしています:\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-7f5ee9d649f146379b5c5fb0140fedc5\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-7f5ee9d649f146379b5c5fb0140fedc5\") {\n", " outputDiv = document.getElementById(\"altair-viz-7f5ee9d649f146379b5c5fb0140fedc5\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}, \"projection\": {\"type\": \"mercator\"}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(alt.topo_feature(world, 'countries')).mark_geoshape(\n", " fill='#2a1d0c', stroke='#706545', strokeWidth=0.5\n", ").project(\n", " type='mercator'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "デフォルトでは、Altairはデータ全体がチャートの幅と高さに収まるように投影を自動的に調整します。さらに、`scale`(ズームレベル)や`translate`(パン)などの投影パラメータを指定して、投影設定をカスタマイズすることができます。以下のコードでは、`scale`と`translate`パラメータを調整して、ヨーロッパに焦点を合わせています:\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-91718abd2285427c868a1d36c8906462\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-91718abd2285427c868a1d36c8906462\") {\n", " outputDiv = document.getElementById(\"altair-viz-91718abd2285427c868a1d36c8906462\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}, \"projection\": {\"scale\": 400, \"translate\": [100, 550], \"type\": \"mercator\"}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(alt.topo_feature(world, 'countries')).mark_geoshape(\n", " fill='#2a1d0c', stroke='#706545', strokeWidth=0.5\n", ").project(\n", " type='mercator', scale=400, translate=[100, 550]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_注意点として、このスケールでデータの110m解像度が明らかになります。より詳細な海岸線や境界線を表示するには、より細かいジオメトリを持つ入力ファイルが必要です。`scale`と`translate`パラメータを調整して、地図を他の地域に焦点を合わせてください!_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "これまでの地図は国だけを表示しています。`layer`オペレーターを使用することで、複数の地図要素を組み合わせることができます。Altairには、追加の地図レイヤーを作成するために使用できる_データ生成器_が含まれています:\n", "\n", "- 球体生成器(`{'sphere': True}`)は、地球全体のGeoJSON表現を提供します。追加の`geoshape`マークを作成して、地球の形を背景レイヤーとして埋め込むことができます。\n", "- 緯度経度線生成器(`{'graticule': ...}`)は、_緯線経線_を表すGeoJSONフィーチャーを作成します。デフォルトの緯線経線は、±80°の緯度ごとに10°ごとの経線と緯線を持っています。極地には、90°ごとの経線があります。これらの設定は、`stepMinor`および`stepMajor`プロパティを使用してカスタマイズできます。\n", "\n", "球体、緯線経線、国のマークをレイヤーとして組み合わせた再利用可能な地図仕様を作成しましょう:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "map = alt.layer(\n", " # use the sphere of the Earth as the base layer\n", " alt.Chart({'sphere': True}).mark_geoshape(\n", " fill='#e6f3ff'\n", " ),\n", " # add a graticule for geographic reference lines\n", " alt.Chart({'graticule': True}).mark_geoshape(\n", " stroke='#ffffff', strokeWidth=1\n", " ),\n", " # and then the countries of the world\n", " alt.Chart(alt.topo_feature(world, 'countries')).mark_geoshape(\n", " fill='#2a1d0c', stroke='#706545', strokeWidth=0.5\n", " )\n", ").properties(\n", " width=600,\n", " height=400\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "地図に希望する投影法を適用し、その結果を描画することができます。ここでは、[ナチュラル・アース投影](https://en.wikipedia.org/wiki/Natural_Earth_projection)を適用しています。_sphere_レイヤーは淡い青色の背景を提供し、_graticule_レイヤーは白い地理的参照線を提供します。" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-a1af16eb42d9496f991ca8d2b7926e27\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-a1af16eb42d9496f991ca8d2b7926e27\") {\n", " outputDiv = document.getElementById(\"altair-viz-a1af16eb42d9496f991ca8d2b7926e27\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 400, \"projection\": {\"scale\": 110, \"translate\": [300, 200], \"type\": \"naturalEarth1\"}, \"width\": 600, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.LayerChart(...)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "map.project(\n", " type='naturalEarth1', scale=110, translate=[300, 200]\n", ").configure_view(stroke=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ポイントマップ\n", "\n", "GeoJSONやTopoJSONファイルで提供される_幾何学的_データに加えて、多くの表形式データセットには、`longitude`(経度)と`latitude`(緯度)の座標を示すフィールドや、国名、州名、郵便番号などの地理的地域への参照が含まれています。これらは[ジオコーディングサービス](https://en.wikipedia.org/wiki/Geocoding)を使用して座標にマッピングできます。場合によっては、位置データが十分に豊富で、データポイントのみを投影することで意味のあるパターンを見ることができます — 基本地図は必要ありません!\n", "\n", "アメリカ合衆国の5桁の郵便番号のデータセットを見てみましょう。このデータセットには、各郵便局の`longitude`、`latitude`座標に加えて、`zip_code`フィールドも含まれています。" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://vega.github.io/vega-datasets/data/zipcodes.csv'" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zipcodes = data.zipcodes.url\n", "zipcodes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "私たちは、各郵便局の位置を小さな(1ピクセル)`square`マークを使って可視化できます。ただし、位置を設定するために、`x`および`y`チャネルは使用しません。_なぜでしょうか?_\n", "\n", "地図投影法は、(経度、緯度)座標を(x、y)座標にマッピングしますが、その方法は任意です。例えば、`longitude`が`x`に、`latitude`が`y`に対応している保証はありません!代わりに、Altairは特別な`longitude`および`latitude`エンコーディングチャネルを提供して、地理的座標を扱います。これらのチャネルは、どのデータフィールドを`longitude`および`latitude`座標にマッピングするかを示し、その後、座標を(x、y)位置にマッピングするための投影を適用します。" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-6f1959dcb1fd4f509e33420826c0a62e\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-6f1959dcb1fd4f509e33420826c0a62e\") {\n", " outputDiv = document.getElementById(\"altair-viz-6f1959dcb1fd4f509e33420826c0a62e\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/zipcodes.csv\"}, \"mark\": {\"type\": \"square\", \"opacity\": 1, \"size\": 1}, \"encoding\": {\"latitude\": {\"field\": \"latitude\", \"type\": \"quantitative\"}, \"longitude\": {\"field\": \"longitude\", \"type\": \"quantitative\"}}, \"height\": 500, \"projection\": {\"type\": \"albersUsa\"}, \"width\": 900, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(zipcodes).mark_square(\n", " size=1, opacity=1\n", ").encode(\n", " longitude='longitude:Q', # apply the field named 'longitude' to the longitude channel\n", " latitude='latitude:Q' # apply the field named 'latitude' to the latitude channel\n", ").project(\n", " type='albersUsa'\n", ").properties(\n", " width=900,\n", " height=500\n", ").configure_view(\n", " stroke=None\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_郵便番号のみをプロットすると、アメリカ合衆国のアウトラインが表示され、基本地図や追加の参照要素なしで郵便局の密度における意味のあるパターンを見分けることができます!_\n", "\n", "ここでは`albersUsa`投影法を使用しています。この投影法は、地球の実際の幾何学にいくつかの自由を与え、アラスカとハワイの縮小版を左下隅に配置しています。投影の`scale`や`translate`パラメータを指定しなかったため、Altairは視覚化されたデータに合わせてこれらを自動的に設定します。\n", "\n", "これで、データセットに対してさらに質問を投げかけることができます。例えば、郵便番号の割り当てには何か規則性があるのでしょうか?この質問を評価するために、郵便番号の最初の数字に基づく色エンコーディングを追加できます。最初に`calculate`変換を追加して最初の数字を抽出し、その結果を色チャネルでエンコードします:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-0a0cb1f1b17646b0ae3cee3ab85471cd\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-0a0cb1f1b17646b0ae3cee3ab85471cd\") {\n", " outputDiv = document.getElementById(\"altair-viz-0a0cb1f1b17646b0ae3cee3ab85471cd\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/zipcodes.csv\"}, \"mark\": {\"type\": \"square\", \"opacity\": 1, \"size\": 2}, \"encoding\": {\"color\": {\"type\": \"nominal\", \"field\": \"digit\"}, \"latitude\": {\"field\": \"latitude\", \"type\": \"quantitative\"}, \"longitude\": {\"field\": \"longitude\", \"type\": \"quantitative\"}}, \"height\": 500, \"projection\": {\"type\": \"albersUsa\"}, \"transform\": [{\"calculate\": \"datum.zip_code[0]\", \"as\": \"digit\"}], \"width\": 900, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(zipcodes).transform_calculate(\n", " digit='datum.zip_code[0]'\n", ").mark_square(\n", " size=2, opacity=1\n", ").encode(\n", " longitude='longitude:Q',\n", " latitude='latitude:Q',\n", " color='digit:N'\n", ").project(\n", " type='albersUsa'\n", ").properties(\n", " width=900,\n", " height=500\n", ").configure_view(\n", " stroke=None\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_特定の数字をズームインするには、フィルター変換を追加して表示するデータを制限します!単一の数字にフィルターを絞り、地図を動的に更新するために[インタラクティブな選択](https://github.com/uwdata/visualization-curriculum/blob/master/altair_interaction.ipynb)を追加してみてください。また、フィルターで数字の値を指定する際は文字列(\\`'1'\\`)を使用してください!_\n", "\n", "(この例は、Ben Fryのクラシックな[zipdecode](https://benfry.com/zipdecode/)ビジュアライゼーションにインスパイアされたものです!)\n", "\n", "さらに、郵便番号の_順序_が何を示しているのかを考えたくなるかもしれません。この質問を探る一つの方法は、連続する郵便番号を`line`マークを使って接続することです。これはRobert Kosaraの[ZipScribble](https://eagereyes.org/zipscribble-maps/united-states)ビジュアライゼーションで行われた方法です:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-01a099ff7a8946d3ad320b125d26ed87\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-01a099ff7a8946d3ad320b125d26ed87\") {\n", " outputDiv = document.getElementById(\"altair-viz-01a099ff7a8946d3ad320b125d26ed87\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/zipcodes.csv\"}, \"mark\": {\"type\": \"line\", \"strokeWidth\": 0.5}, \"encoding\": {\"color\": {\"type\": \"nominal\", \"field\": \"digit\"}, \"latitude\": {\"field\": \"latitude\", \"type\": \"quantitative\"}, \"longitude\": {\"field\": \"longitude\", \"type\": \"quantitative\"}, \"order\": {\"type\": \"ordinal\", \"field\": \"zip_code\"}}, \"height\": 500, \"projection\": {\"type\": \"albersUsa\"}, \"transform\": [{\"filter\": \"-150 < datum.longitude && 22 < datum.latitude && datum.latitude < 55\"}, {\"calculate\": \"datum.zip_code[0]\", \"as\": \"digit\"}], \"width\": 900, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(zipcodes).transform_filter(\n", " '-150 < datum.longitude && 22 < datum.latitude && datum.latitude < 55'\n", ").transform_calculate(\n", " digit='datum.zip_code[0]'\n", ").mark_line(\n", " strokeWidth=0.5\n", ").encode(\n", " longitude='longitude:Q',\n", " latitude='latitude:Q',\n", " color='digit:N',\n", " order='zip_code:O'\n", ").project(\n", " type='albersUsa'\n", ").properties(\n", " width=900,\n", " height=500\n", ").configure_view(\n", " stroke=None\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_これで、郵便番号がさらに小さなエリアにクラスター化されている様子が見え、場所によるコードの階層的な割り当てが示されていますが、地域内で顕著なばらつきもあります。_\n", "\n", "以前の地図に注意深く見ていた場合、左上隅にプロットされた郵便番号があることに気付いたかもしれません!これらはプエルトリコやアメリカ領サモアのような場所に対応しており、`albersUsa`投影法によって`null`座標(`0`, `0`)にマッピングされています。さらに、アラスカとハワイは接続された線分の表示を複雑にする可能性があります。この対応として、上記のコードには選択した`longitude`および`latitude`範囲の外にある点を削除する追加のフィルターが含まれています。\n", "\n", "_上記のフィルターを削除して、何が起こるかを見てみてください!_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## シンボル・マップ" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "今度は、ベースマップとプロットされたデータを別々のレイヤーとして組み合わせます。アメリカの商業航空ネットワークを、空港とフライトルートの両方を考慮して調べます。そのために、3つのデータセットが必要です。\n", "ベースマップには、`states`または`counties`の特徴を含む、解像度10mの米国のTopoJSONファイルを使用します:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://vega.github.io/vega-datasets/data/us-10m.json'" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "usa = data.us_10m.url\n", "usa" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "空港については、各空港の`longitude`(経度)と`latitude`(緯度)の座標、そして`iata`空港コード(例:`'SEA'`は[シアトル・タコマ国際空港](https://en.wikipedia.org/wiki/Seattle%E2%80%93Tacoma_International_Airport)を示します)を含むデータセットを使用します。" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://vega.github.io/vega-datasets/data/airports.csv'" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "airports = data.airports.url\n", "airports" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "最後に、フライトルートのデータセットを使用します。このデータセットには、対応する空港のIATAコードを含む`origin`(出発地)と`destination`(到着地)のフィールドがあります。" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://vega.github.io/vega-datasets/data/flights-airport.csv'" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "flights = data.flights_airport.url\n", "flights" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "基本地図を作成し、`albersUsa` 投影法を使用して、各空港の `circle` マークをプロットするところから始めましょう。" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-8f751ecfdbab48a7a5ed3ac927206134\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-8f751ecfdbab48a7a5ed3ac927206134\") {\n", " outputDiv = document.getElementById(\"altair-viz-8f751ecfdbab48a7a5ed3ac927206134\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"layer\": [{\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/us-10m.json\", \"format\": {\"feature\": \"states\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#ddd\", \"stroke\": \"#fff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/airports.csv\"}, \"mark\": {\"type\": \"circle\", \"size\": 9}, \"encoding\": {\"latitude\": {\"field\": \"latitude\", \"type\": \"quantitative\"}, \"longitude\": {\"field\": \"longitude\", \"type\": \"quantitative\"}, \"tooltip\": {\"type\": \"nominal\", \"field\": \"iata\"}}}], \"height\": 500, \"projection\": {\"type\": \"albersUsa\"}, \"width\": 900, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.LayerChart(...)" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.layer(\n", " alt.Chart(alt.topo_feature(usa, 'states')).mark_geoshape(\n", " fill='#ddd', stroke='#fff', strokeWidth=1\n", " ),\n", " alt.Chart(airports).mark_circle(size=9).encode(\n", " latitude='latitude:Q',\n", " longitude='longitude:Q',\n", " tooltip='iata:N'\n", " )\n", ").project(\n", " type='albersUsa'\n", ").properties(\n", " width=900,\n", " height=500\n", ").configure_view(\n", " stroke=None\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "申し訳ありません。以下が翻訳された全文です。\n", "\n", "---\n", "\n", "最初に、`albersUsa` 投影法を使ってベースマップを作成し、各空港の位置を `circle` マークで表示するレイヤーを追加します。\n", "\n", "_それは多くの空港ですね!もちろん、すべてが主要なハブ空港というわけではありません。_\n", "\n", "先ほどの郵便番号データセットと同様に、空港データには本土以外の場所にある点も含まれています。そのため、再び左上隅に点が表示されています。これらの点をフィルタリングしたい場合、まずその位置を知る必要があります。\n", "\n", "_上記の地図投影法を `albers` に変更してみてください。これにより、`albersUsa` の特異な挙動を回避し、これらの追加のポイントが実際にどこにあるかが確認できるようになります!_\n", "\n", "次に、すべての空港を均等に表示するのではなく、各空港から発着する便の総数を考慮して主要なハブ空港を特定しましょう。`routes` データセットを主なデータソースとして利用します。これには、各 `origin` 空港からの便数を集計したリストが含まれています。\n", "\n", "しかし、`routes` データセットには空港の位置情報が含まれていません!そのため、`routes` データに位置情報を追加するために、新しいデータ変換である `lookup` を使用する必要があります。`lookup` 変換は、主データセットのフィールド値をキーとして使用し、別のテーブルから関連情報を検索します。この場合、`routes` データセットの `origin` 空港コードを `airports` データセットの `iata` フィールドと照合し、対応する `latitude` と `longitude` の情報を抽出します。" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-4da692dd5343499e8a82cdf5a792e72d\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-4da692dd5343499e8a82cdf5a792e72d\") {\n", " outputDiv = document.getElementById(\"altair-viz-4da692dd5343499e8a82cdf5a792e72d\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"layer\": [{\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/us-10m.json\", \"format\": {\"feature\": \"states\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#ddd\", \"stroke\": \"#fff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/flights-airport.csv\"}, \"mark\": \"circle\", \"encoding\": {\"latitude\": {\"field\": \"latitude\", \"type\": \"quantitative\"}, \"longitude\": {\"field\": \"longitude\", \"type\": \"quantitative\"}, \"order\": {\"type\": \"quantitative\", \"field\": \"routes\", \"sort\": \"descending\"}, \"size\": {\"type\": \"quantitative\", \"field\": \"routes\", \"legend\": null, \"scale\": {\"range\": [0, 1000]}}, \"tooltip\": [{\"type\": \"nominal\", \"field\": \"origin\"}, {\"type\": \"quantitative\", \"field\": \"routes\"}]}, \"transform\": [{\"aggregate\": [{\"op\": \"count\", \"as\": \"routes\"}], \"groupby\": [\"origin\"]}, {\"lookup\": \"origin\", \"from\": {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/airports.csv\"}, \"key\": \"iata\", \"fields\": [\"state\", \"latitude\", \"longitude\"]}}, {\"filter\": \"datum.state !== \\\"PR\\\" && datum.state !== \\\"VI\\\"\"}]}], \"height\": 500, \"projection\": {\"type\": \"albersUsa\"}, \"width\": 900, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.LayerChart(...)" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.layer(\n", " alt.Chart(alt.topo_feature(usa, 'states')).mark_geoshape(\n", " fill='#ddd', stroke='#fff', strokeWidth=1\n", " ),\n", " alt.Chart(flights).mark_circle().transform_aggregate(\n", " groupby=['origin'],\n", " routes='count()'\n", " ).transform_lookup(\n", " lookup='origin',\n", " from_=alt.LookupData(data=airports, key='iata',\n", " fields=['state', 'latitude', 'longitude'])\n", " ).transform_filter(\n", " 'datum.state !== \"PR\" && datum.state !== \"VI\"'\n", " ).encode(\n", " latitude='latitude:Q',\n", " longitude='longitude:Q',\n", " tooltip=['origin:N', 'routes:Q'],\n", " size=alt.Size('routes:Q', scale=alt.Scale(range=[0, 1000]), legend=None),\n", " order=alt.Order('routes:Q', sort='descending')\n", " )\n", ").project(\n", " type='albersUsa'\n", ").properties(\n", " width=900,\n", " height=500\n", ").configure_view(\n", " stroke=None\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_どの米国の空港が最も多くの出発ルートを持っているか?_\n", "\n", "空港が表示できたので、空港間の空の交通ネットワークをよりよく理解するために、インタラクティブに操作したいかもしれません。`origin` 空港から `destination` 空港へのルートを示す `rule` マークレイヤーを追加することができます。これには、各エンドポイントの座標を取得するために 2 回の `lookup` 変換が必要です。また、`single` 選択を使用してこれらのルートをフィルタリングし、現在選択された空港から出発するルートのみを表示できるようにします。\n", "\n", "_上記の静的な地図から、インタラクティブ版を作成するにはどうすればよいでしょうか?コードの部分をスキップして、インタラクティブな地図に触れ、どのように作成するかを考えてみてください!_" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-d38f36d4ef0a4fb191dd3c3014624e8f\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-d38f36d4ef0a4fb191dd3c3014624e8f\") {\n", " outputDiv = document.getElementById(\"altair-viz-d38f36d4ef0a4fb191dd3c3014624e8f\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"layer\": [{\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/us-10m.json\", \"format\": {\"feature\": \"states\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#ddd\", \"stroke\": \"#fff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/flights-airport.csv\"}, \"mark\": {\"type\": \"rule\", \"color\": \"#000\", \"opacity\": 0.35}, \"encoding\": {\"latitude\": {\"field\": \"latitude\", \"type\": \"quantitative\"}, \"latitude2\": {\"field\": \"lat2\"}, \"longitude\": {\"field\": \"longitude\", \"type\": \"quantitative\"}, \"longitude2\": {\"field\": \"lon2\"}}, \"transform\": [{\"filter\": {\"selection\": \"selector001\"}}, {\"lookup\": \"origin\", \"from\": {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/airports.csv\"}, \"key\": \"iata\", \"fields\": [\"latitude\", \"longitude\"]}}, {\"lookup\": \"destination\", \"as\": [\"lat2\", \"lon2\"], \"from\": {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/airports.csv\"}, \"key\": \"iata\", \"fields\": [\"latitude\", \"longitude\"]}}]}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/flights-airport.csv\"}, \"mark\": \"circle\", \"encoding\": {\"latitude\": {\"field\": \"latitude\", \"type\": \"quantitative\"}, \"longitude\": {\"field\": \"longitude\", \"type\": \"quantitative\"}, \"order\": {\"type\": \"quantitative\", \"field\": \"routes\", \"sort\": \"descending\"}, \"size\": {\"type\": \"quantitative\", \"field\": \"routes\", \"legend\": null, \"scale\": {\"range\": [0, 1000]}}, \"tooltip\": [{\"type\": \"nominal\", \"field\": \"origin\"}, {\"type\": \"quantitative\", \"field\": \"routes\"}]}, \"selection\": {\"selector001\": {\"type\": \"single\", \"on\": \"mouseover\", \"nearest\": true, \"fields\": [\"origin\"], \"empty\": \"none\"}}, \"transform\": [{\"aggregate\": [{\"op\": \"count\", \"as\": \"routes\"}], \"groupby\": [\"origin\"]}, {\"lookup\": \"origin\", \"from\": {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/airports.csv\"}, \"key\": \"iata\", \"fields\": [\"state\", \"latitude\", \"longitude\"]}}, {\"filter\": \"datum.state !== \\\"PR\\\" && datum.state !== \\\"VI\\\"\"}]}], \"height\": 500, \"projection\": {\"type\": \"albersUsa\"}, \"width\": 900, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.LayerChart(...)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# interactive selection for origin airport\n", "# select nearest airport to mouse cursor\n", "origin = alt.selection_single(\n", " on='mouseover', nearest=True,\n", " fields=['origin'], empty='none'\n", ")\n", "\n", "# shared data reference for lookup transforms\n", "foreign = alt.LookupData(data=airports, key='iata',\n", " fields=['latitude', 'longitude'])\n", " \n", "alt.layer(\n", " # base map of the United States\n", " alt.Chart(alt.topo_feature(usa, 'states')).mark_geoshape(\n", " fill='#ddd', stroke='#fff', strokeWidth=1\n", " ),\n", " # route lines from selected origin airport to destination airports\n", " alt.Chart(flights).mark_rule(\n", " color='#000', opacity=0.35\n", " ).transform_filter(\n", " origin # filter to selected origin only\n", " ).transform_lookup(\n", " lookup='origin', from_=foreign # origin lat/lon\n", " ).transform_lookup(\n", " lookup='destination', from_=foreign, as_=['lat2', 'lon2'] # dest lat/lon\n", " ).encode(\n", " latitude='latitude:Q',\n", " longitude='longitude:Q',\n", " latitude2='lat2',\n", " longitude2='lon2',\n", " ),\n", " # size airports by number of outgoing routes\n", " # 1. aggregate flights-airport data set\n", " # 2. lookup location data from airports data set\n", " # 3. remove Puerto Rico (PR) and Virgin Islands (VI)\n", " alt.Chart(flights).mark_circle().transform_aggregate(\n", " groupby=['origin'],\n", " routes='count()'\n", " ).transform_lookup(\n", " lookup='origin',\n", " from_=alt.LookupData(data=airports, key='iata',\n", " fields=['state', 'latitude', 'longitude'])\n", " ).transform_filter(\n", " 'datum.state !== \"PR\" && datum.state !== \"VI\"'\n", " ).add_selection(\n", " origin\n", " ).encode(\n", " latitude='latitude:Q',\n", " longitude='longitude:Q',\n", " tooltip=['origin:N', 'routes:Q'],\n", " size=alt.Size('routes:Q', scale=alt.Scale(range=[0, 1000]), legend=None),\n", " order=alt.Order('routes:Q', sort='descending') # place smaller circles on top\n", " )\n", ").project(\n", " type='albersUsa'\n", ").properties(\n", " width=900,\n", " height=500\n", ").configure_view(\n", " stroke=None\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_Mouseover the map to probe the flight network!_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## コロプレス・マップ" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[コロプレス・マップ](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%AD%E3%83%97%E3%83%AC%E3%82%B9%E5%9B%B3)は、データ値を視覚化するためにシェーディングやテクスチャを使用する地図です。サイズを調整したシンボルマップは、円の面積の比例的な差異を色のシェードで比較するよりも人々がより正確に読み取れるため、しばしば優れています。それにもかかわらず、コロプレス・マップは実際に人気があり、シンボルが多すぎる場合に視覚的に圧倒されることを避けるのに特に有用です。\n", "\n", "たとえば、アメリカ合衆国には50の州しかありませんが、その中には数千の郡があります。2008年の不況の年における郡別の失業率のコロプレス・マップを作成してみましょう。場合によっては、入力GeoJSONまたはTopoJSONファイルに、直接視覚化できる統計データが含まれていることがあります。しかしこの場合、2つのファイルがあります。郡の境界特徴(`usa`)が含まれているTopoJSONファイルと、失業統計が含まれている別のテキストファイルです:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://vega.github.io/vega-datasets/data/unemployment.tsv'" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "unemp = data.unemployment.url\n", "unemp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "データソースを統合するために、再度`lookup`変換を使用し、TopoJSONベースの`geoshape`データに失業率を追加する必要があります。その後、`rate`フィールドを参照する`color`エンコーディングを含む地図を作成できます。" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-9724050db4af41eaa0d80ae804f40430\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-9724050db4af41eaa0d80ae804f40430\") {\n", " outputDiv = document.getElementById(\"altair-viz-9724050db4af41eaa0d80ae804f40430\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/us-10m.json\", \"format\": {\"feature\": \"counties\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#aaa\", \"strokeWidth\": 0.25}, \"encoding\": {\"color\": {\"type\": \"quantitative\", \"field\": \"rate\", \"legend\": {\"format\": \"%\"}, \"scale\": {\"clamp\": true, \"domain\": [0, 0.3]}}, \"tooltip\": {\"type\": \"quantitative\", \"field\": \"rate\", \"format\": \".0%\"}}, \"height\": 500, \"projection\": {\"type\": \"albersUsa\"}, \"transform\": [{\"lookup\": \"id\", \"from\": {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/unemployment.tsv\"}, \"key\": \"id\", \"fields\": [\"rate\"]}}], \"width\": 900, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.Chart(...)" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "alt.Chart(alt.topo_feature(usa, 'counties')).mark_geoshape(\n", " stroke='#aaa', strokeWidth=0.25\n", ").transform_lookup(\n", " lookup='id', from_=alt.LookupData(data=unemp, key='id', fields=['rate'])\n", ").encode(\n", " alt.Color('rate:Q',\n", " scale=alt.Scale(domain=[0, 0.3], clamp=True), \n", " legend=alt.Legend(format='%')),\n", " alt.Tooltip('rate:Q', format='.0%')\n", ").project(\n", " type='albersUsa'\n", ").properties(\n", " width=900,\n", " height=500\n", ").configure_view(\n", " stroke=None\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*郡ごとの失業率を調べてみましょう。ミシガン州の高い値は自動車産業に関連している可能性があります。グレートプレーンズや山岳地帯の郡では、低い値と高い値の両方が見られます。この変動は意味があるのでしょうか、それとも[サンプルサイズが小さいことによるアーティファクト](https://medium.com/@uwdata/surprise-maps-showing-the-unexpected-e92b67398865)かもしれません。さらに調べるために、色のマッピングを調整するために上限スケールのドメイン(例えば`0.2`)を変更してみてください。*\n", "\n", "*コロプレスマップでの中心的な懸念は色の選択です。上記では、Altairのデフォルトの`'yellowgreenblue'`スキームを使用してヒートマップを作成しています。以下では、他のスキームを比較します。例えば、明度だけが変化する**単一色の順次スキーム**(`teals`)、明度と色相の両方が変化する**多色の順次スキーム**(`viridis`)、および白い中間点を使用する**分岐スキーム**(`blueorange`)です。:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-c71c1682a5d2442cb0fa2afa90e6d559\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-c71c1682a5d2442cb0fa2afa90e6d559\") {\n", " outputDiv = document.getElementById(\"altair-viz-c71c1682a5d2442cb0fa2afa90e6d559\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"hconcat\": [{\"mark\": \"geoshape\", \"encoding\": {\"color\": {\"type\": \"quantitative\", \"field\": \"rate\", \"legend\": null, \"scale\": {\"scheme\": \"tealblues\"}}}, \"height\": 200, \"projection\": {\"type\": \"albersUsa\"}, \"width\": 305}, {\"mark\": \"geoshape\", \"encoding\": {\"color\": {\"type\": \"quantitative\", \"field\": \"rate\", \"legend\": null, \"scale\": {\"scheme\": \"viridis\"}}}, \"height\": 200, \"projection\": {\"type\": \"albersUsa\"}, \"width\": 305}, {\"mark\": \"geoshape\", \"encoding\": {\"color\": {\"type\": \"quantitative\", \"field\": \"rate\", \"legend\": null, \"scale\": {\"scheme\": \"blueorange\"}}}, \"height\": 200, \"projection\": {\"type\": \"albersUsa\"}, \"width\": 305}], \"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/us-10m.json\", \"format\": {\"feature\": \"counties\", \"type\": \"topojson\"}}, \"resolve\": {\"scale\": {\"color\": \"independent\"}}, \"transform\": [{\"lookup\": \"id\", \"from\": {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/unemployment.tsv\"}, \"key\": \"id\", \"fields\": [\"rate\"]}}], \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.HConcatChart(...)" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# utility function to generate a map specification for a provided color scheme\n", "def map_(scheme):\n", " return alt.Chart().mark_geoshape().project(type='albersUsa').encode(\n", " alt.Color('rate:Q', scale=alt.Scale(scheme=scheme), legend=None)\n", " ).properties(width=305, height=200)\n", "\n", "alt.hconcat(\n", " map_('tealblues'), map_('viridis'), map_('blueorange'),\n", " data=alt.topo_feature(usa, 'counties')\n", ").transform_lookup(\n", " lookup='id', from_=alt.LookupData(data=unemp, key='id', fields=['rate'])\n", ").configure_view(\n", " stroke=None\n", ").resolve_scale(\n", " color='independent'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_どのカラースキームがより効果的だと感じますか?その理由は何でしょうか?上記のマップを、[Vegaカラースキームのドキュメント](https://vega.github.io/vega/docs/schemes/)に記載されている他の利用可能なスキームを使用するように変更してみてください。_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cartographic Projections" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "これで地図作成に関する経験が得られましたので、カートグラフィック投影法についてもう少し詳しく見ていきましょう。[Wikipedia](https://en.wikipedia.org/wiki/Map_projection)によると、\n", "\n", "> _すべての地図投影法は必然的に何らかの形で表面を歪めます。地図の目的に応じて、いくつかの歪みは許容され、他のものは許容されないため、球体のような物体のいくつかの特性を保持するために他の特性を犠牲にする異なる地図投影法が存在します。_\n", "\n", "考慮すべきいくつかの特性には次のようなものがあります:\n", "\n", "- _面積_: 投影法は地域のサイズを歪めますか?\n", "- _方位_: 直線が一定の進行方向を示すことがありますか?\n", "- _距離_: 等しい長さの線が地球上で等しい距離に対応しますか?\n", "- _形状_: 投影法は点間の空間的関係(角度)を保持しますか?\n", "\n", "適切な投影法の選択は、地図の使用目的によって異なります。たとえば、土地利用や土地の広がりが重要な場合、面積を保持する投影法を選ぶかもしれません。地震の震源地から放射される衝撃波を視覚化したい場合、その点からの距離を保持することに焦点を当てるかもしれません。また、航海を支援したい場合、方位や形状の保持が重要になることがあります。\n", "\n", "投影法はまた、_投影面_という観点で特徴付けることもできます。たとえば、円筒投影法は球体の表面点を周囲の円筒に投影し、「広げた」円筒が地図として提供されます。次に説明するように、円錐面(円錐投影)や平面(方位投影)に直接投影することもあります。\n", "\n", "*まずはさまざまな投影法を操作して直感を養いましょう!**[オンラインVega-Liteカートグラフィック投影ノートブックを開く](https://observablehq.com/@vega/vega-lite-cartographic-projections)。* ページ上のコントロールを使用して、投影法を選択し、`scale`(ズーム)やx/yの平行移動(パン)などの投影パラメータを探ってみてください。回転([ヨー、ピッチ、ロール](https://en.wikipedia.org/wiki/Aircraft_principal_axes))コントロールは、地球が投影面に対してどのように向いているかを決定します。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 特定の投影法の紹介" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[**円筒投影法**](https://en.wikipedia.org/wiki/Map_projection#Cylindrical)は、球体を周囲の円筒に投影し、その後円筒を広げて地図にします。円筒の主要軸が南北方向に向いている場合、経線は直線にマッピングされます。\n", "\n", "[擬似円筒投影法](https://en.wikipedia.org/wiki/Map_projection#Pseudocylindrical)では、中央経線を直線として表現し、他の経線は中央から「曲がって」いきます。" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-8aea4908bad0402990979b00fc6f660c\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-8aea4908bad0402990979b00fc6f660c\") {\n", " outputDiv = document.getElementById(\"altair-viz-8aea4908bad0402990979b00fc6f660c\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"hconcat\": [{\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 225, \"projection\": {\"type\": \"equirectangular\"}, \"title\": \"equirectangular\", \"width\": 225}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 225, \"projection\": {\"type\": \"mercator\"}, \"title\": \"mercator\", \"width\": 225}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 225, \"projection\": {\"type\": \"transverseMercator\"}, \"title\": \"transverseMercator\", \"width\": 225}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 225, \"projection\": {\"type\": \"naturalEarth1\"}, \"title\": \"naturalEarth1\", \"width\": 225}], \"spacing\": 10, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.HConcatChart(...)" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "minimap = map.properties(width=225, height=225)\n", "alt.hconcat(\n", " minimap.project(type='equirectangular').properties(title='equirectangular'),\n", " minimap.project(type='mercator').properties(title='mercator'),\n", " minimap.project(type='transverseMercator').properties(title='transverseMercator'),\n", " minimap.project(type='naturalEarth1').properties(title='naturalEarth1')\n", ").properties(spacing=10).configure_view(stroke=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [正距円筒図法](https://en.wikipedia.org/wiki/Equirectangular_projection) (`equirectangular`): 緯度、経度の座標値を直接スケールします。\n", "- [メルカトル図法](https://en.wikipedia.org/wiki/Mercator_projection) (`mercator`): 円筒に投影し、経度はそのまま使用しますが、緯度は非線形変換を受けます。直線は一定の羅針盤の方向([rhumb lines](https://en.wikipedia.org/wiki/Rhumb_line))を保持し、航海に適した投影法です。ただし、極地域では面積の歪みが大きくなります。\n", "- [横メルカトル図法](https://en.wikipedia.org/wiki/Transverse_Mercator_projection) (`transverseMercator`): メルカトル図法の一種で、円筒の軸が横向きに回転したものです。標準のメルカトル図法では赤道付近で最も正確ですが、横メルカトル図法では中央子午線に沿った部分で最も正確です。\n", "- [ナチュラルアース図法](https://en.wikipedia.org/wiki/Natural_Earth_projection) (`naturalEarth1`): 地球全体を一度に表示するために設計された擬似円筒図法です。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[**円錐図法**](https://en.wikipedia.org/wiki/Map_projection#Conic)は、球体を円錐に投影し、その円錐を平面に展開します。円錐図法は、球体と円錐が交差する場所を決定する2つの**標準平行線**によって構成されます。" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-9c71b3671425430b9c4daf61d10bc3f5\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-9c71b3671425430b9c4daf61d10bc3f5\") {\n", " outputDiv = document.getElementById(\"altair-viz-9c71b3671425430b9c4daf61d10bc3f5\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"hconcat\": [{\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 130, \"projection\": {\"type\": \"conicEqualArea\"}, \"title\": \"conicEqualArea\", \"width\": 180}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 130, \"projection\": {\"type\": \"conicEquidistant\"}, \"title\": \"conicEquidistant\", \"width\": 180}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 130, \"projection\": {\"scale\": 35, \"translate\": [90, 65], \"type\": \"conicConformal\"}, \"title\": \"conicConformal\", \"width\": 180}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 130, \"projection\": {\"type\": \"albers\"}, \"title\": \"albers\", \"width\": 180}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 130, \"projection\": {\"type\": \"albersUsa\"}, \"title\": \"albersUsa\", \"width\": 180}], \"spacing\": 10, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.HConcatChart(...)" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "minimap = map.properties(width=180, height=130)\n", "alt.hconcat(\n", " minimap.project(type='conicEqualArea').properties(title='conicEqualArea'),\n", " minimap.project(type='conicEquidistant').properties(title='conicEquidistant'),\n", " minimap.project(type='conicConformal', scale=35, translate=[90,65]).properties(title='conicConformal'),\n", " minimap.project(type='albers').properties(title='albers'),\n", " minimap.project(type='albersUsa').properties(title='albersUsa')\n", ").properties(spacing=10).configure_view(stroke=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [**円錐等面積図法**](https://en.wikipedia.org/wiki/Albers_projection) (`conicEqualArea`): 面積を保持する円錐図法。形状や距離は保持されませんが、標準平行線内ではおおよそ正確です。\n", "- [**円錐等距離図法**](https://en.wikipedia.org/wiki/Equidistant_conic_projection) (`conicEquidistant`): 経線および標準平行線に沿った距離を保持する円錐図法。\n", "- [**円錐等角図法**](https://en.wikipedia.org/wiki/Lambert_conformal_conic_projection) (`conicConformal`): 形状(局所的な角度)を保持する円錐図法。ただし、面積や距離は保持されません。\n", "- [**アルバース図法**](https://en.wikipedia.org/wiki/Albers_projection) (`albers`): アメリカ合衆国の地図作成に最適化された標準平行線を持つ円錐等面積図法の一種。\n", "- [**アルバースUSA図法**](https://en.wikipedia.org/wiki/Albers_projection) (`albersUsa`): アメリカ合衆国の50州のためのハイブリッド投影法。大陸部、アラスカ、ハワイそれぞれの異なるパラメーターで3つのアルバース投影を組み合わせています。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[**方位図法**](https://en.wikipedia.org/wiki/Map_projection#Azimuthal_%28projections_onto_a_plane%29) は、球面を直接平面に投影する図法です。" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "<div id=\"altair-viz-7fcbc152f47a44289d745bee6c35c5be\"></div>\n", "<script type=\"text/javascript\">\n", " (function(spec, embedOpt){\n", " let outputDiv = document.currentScript.previousElementSibling;\n", " if (outputDiv.id !== \"altair-viz-7fcbc152f47a44289d745bee6c35c5be\") {\n", " outputDiv = document.getElementById(\"altair-viz-7fcbc152f47a44289d745bee6c35c5be\");\n", " }\n", " const paths = {\n", " \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n", " \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n", " \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.8.1?noext\",\n", " \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n", " };\n", "\n", " function loadScript(lib) {\n", " return new Promise(function(resolve, reject) {\n", " var s = document.createElement('script');\n", " s.src = paths[lib];\n", " s.async = true;\n", " s.onload = () => resolve(paths[lib]);\n", " s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " });\n", " }\n", "\n", " function showError(err) {\n", " outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n", " throw err;\n", " }\n", "\n", " function displayChart(vegaEmbed) {\n", " vegaEmbed(outputDiv, spec, embedOpt)\n", " .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n", " }\n", "\n", " if(typeof define === \"function\" && define.amd) {\n", " requirejs.config({paths});\n", " require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n", " } else if (typeof vegaEmbed === \"function\") {\n", " displayChart(vegaEmbed);\n", " } else {\n", " loadScript(\"vega\")\n", " .then(() => loadScript(\"vega-lite\"))\n", " .then(() => loadScript(\"vega-embed\"))\n", " .catch(showError)\n", " .then(() => displayChart(vegaEmbed));\n", " }\n", " })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300, \"stroke\": null}}, \"hconcat\": [{\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 180, \"projection\": {\"type\": \"azimuthalEqualArea\"}, \"title\": \"azimuthalEqualArea\", \"width\": 180}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 180, \"projection\": {\"type\": \"azimuthalEquidistant\"}, \"title\": \"azimuthalEquidistant\", \"width\": 180}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 180, \"projection\": {\"type\": \"orthographic\"}, \"title\": \"orthographic\", \"width\": 180}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 180, \"projection\": {\"type\": \"stereographic\"}, \"title\": \"stereographic\", \"width\": 180}, {\"layer\": [{\"data\": {\"sphere\": true}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#e6f3ff\"}}, {\"data\": {\"graticule\": true}, \"mark\": {\"type\": \"geoshape\", \"stroke\": \"#ffffff\", \"strokeWidth\": 1}}, {\"data\": {\"url\": \"https://vega.github.io/vega-datasets/data/world-110m.json\", \"format\": {\"feature\": \"countries\", \"type\": \"topojson\"}}, \"mark\": {\"type\": \"geoshape\", \"fill\": \"#2a1d0c\", \"stroke\": \"#706545\", \"strokeWidth\": 0.5}}], \"height\": 180, \"projection\": {\"type\": \"gnomonic\"}, \"title\": \"gnomonic\", \"width\": 180}], \"spacing\": 10, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.8.1.json\"}, {\"mode\": \"vega-lite\"});\n", "</script>" ], "text/plain": [ "alt.HConcatChart(...)" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "minimap = map.properties(width=180, height=180)\n", "alt.hconcat(\n", " minimap.project(type='azimuthalEqualArea').properties(title='azimuthalEqualArea'),\n", " minimap.project(type='azimuthalEquidistant').properties(title='azimuthalEquidistant'),\n", " minimap.project(type='orthographic').properties(title='orthographic'),\n", " minimap.project(type='stereographic').properties(title='stereographic'),\n", " minimap.project(type='gnomonic').properties(title='gnomonic')\n", ").properties(spacing=10).configure_view(stroke=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [方位等面投影](https://en.wikipedia.org/wiki/Lambert_azimuthal_equal-area_projection) (`azimuthalEqualArea`): 地球全体で面積を正確に投影しますが、形状(局所的な角度)は保存しません。\n", "- [方位等距離投影](https://en.wikipedia.org/wiki/Azimuthal_equidistant_projection) (`azimuthalEquidistant`): 投影中心から地球上の他のすべての点への比例距離を保持します。\n", "- [直射投影](https://en.wikipedia.org/wiki/Orthographic_projection_in_cartography) (`orthographic`): 可視の半球を遠くの平面に投影します。外宇宙から見た地球の視点に近いものです。\n", "- [立体投影](https://en.wikipedia.org/wiki/Stereographic_projection) (`stereographic`): 形状は保存しますが、面積や距離は保存しません。\n", "- [球面直投影](https://en.wikipedia.org/wiki/Gnomonic_projection) (`gnomonic`): 球面の表面を接線平面に直接投影します。地球を取り巻く[大円](https://en.wikipedia.org/wiki/Great_circle)が直線として投影され、最短経路を示します。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## コーダ:地理データの整形" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "上記の例はすべて、vega-datasetsコレクションから取得したデータで、ジオメトリ(TopoJSON)および表形式データ(空港、失業率)を含んでいます。地理的視覚化を始めるための一般的な課題は、タスクに必要なデータを収集することです。多くのデータ提供者が存在し、たとえば[アメリカ合衆国地質調査所(USGS)](https://www.usgs.gov/products/data/all-data)や[米国国勢調査局(U.S. Census Bureau)](https://www.census.gov/data/datasets.html)などのサービスがあります。\n", "\n", "多くの場合、すでに地理的な要素を含むデータがあり、追加の測定値やジオメトリが必要です。以下は、データ収集を始めるための一つのワークフローです:\n", "\n", "1. [Natural Earth Data](http://www.naturalearthdata.com/downloads/)にアクセスし、興味のある地域と解像度のデータを選択します。対応するzipファイルをダウンロードします。\n", "2. [MapShaper](https://mapshaper.org/)に移動し、ダウンロードしたzipファイルをページにドロップします。データを必要に応じて修正し、生成されたTopoJSONまたはGeoJSONファイルを「エクスポート」します。\n", "3. MapShaperからエクスポートされたデータをAltairで使用するために読み込みます!\n", "\n", "もちろん、地理的データを扱うための他にも多くのツール(オープンソースや商用)が存在します。地理データの操作や地図作成については、マイク・ボストックによる[コマンドライン・カートグラフィー](https://medium.com/@mbostock/command-line-cartography-part-1-897aa8f8ca2c)のチュートリアルシリーズを参照してください。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## まとめ\n", "\n", "ここまでで、私たちは地図作成のほんの一端に触れただけです。(まさか、1つのノートブックで何世紀もの学びを伝授できると期待していたわけではないですよね?)例えば、私たちは[_カートグラム_](https://en.wikipedia.org/wiki/Cartogram)や[_地形_](https://en.wikipedia.org/wiki/Topography)の伝達—例えば、イムホフの啓発的な著書『[_Cartographic Relief Presentation_](https://books.google.com/books?id=cVy1Ms43fFYC)』—といったトピックには触れていません。しかし、これで、豊かな地理的可視化を作成するための基礎は十分に学びました。さらに学びたい場合は、マクエアレンの著書『[_How Maps Work: Representation, Visualization, and Design_](https://books.google.com/books?id=xhAvN3B0CkUC)』が、データ可視化の視点から地図作成の貴重な概要を提供しています。" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.1" } }, "nbformat": 4, "nbformat_minor": 4 }