R、GIS、fuzzyjoin:NUTSリージョンの統計の復元

この投稿では、デンマークの地域の人口統計データを復元した方法について説明します。2007年の領土構造の改革後、データの公式な調和はありませんでした。 これは、 私のphdプロジェクトの一環として実行したEurostatデータの調和のほんの一部です。 この投稿は、最初に英語のブログDemotrendsブログで公開されました 。 人口統計学者だけでなく興味深いものになると思います。







NUTSとは?



NUTSは、 統計の領土単位の命名法を表します。 これは、欧州連合の国で採用されている行政区域区分の標準化されたシステムです。 この問題の歴史は1970年代にさかのぼり、ヨーロッパのさまざまな国の地域を比較できるようにするためのアイデアが生まれました。 多かれ少なかれ完全に広く使用されている形式では、システムは世紀の変わり目にのみ現れました。 NUTSには3つの主要なレベルがあり(図1を参照)、地域分析ではNUTS-2が最も一般的です。







fig1

図1.異なる階層レベルのNUTS領域を区別する原理の図







NUTSの主な目的は、異なる階層レベルの地域を区別するための比較可能な基準を作成することでした。 それにもかかわらず、2013年のNUTS-2レベルレギンの人口は28.5千人でした。 (フィンランドのオーランド島 )から1200万人近くまで( イルドフランス 、フランス-パリとその周辺)。







互換性のないデータシリーズ



本質的に非常に条件付きであるが、行政と領土の区分は変わらないままではなく、絶えず変化している。 領土システムの変更は、地域レベルでのプロセスの長期分析に大きな困難をもたらします。古い地域と新しい地域のマッチングは骨の折れる作業であり、常に明確に解決される作業ではないためです(興味がある場合は、私の古い作品でロシアの地域見ることができます-付録1と2)。 しかし、地域統計の明らかな困難にもかかわらず、国境はさまざまなレベルの州の管理者の要件に従って絶えず変化しています。 Eurostat 、NUTS地域で発生したすべての変更を記録し、詳細な説明を公開します(図2)。







fig2

図2.バージョン2006と2010の間のNUTSシステムの変更







それにもかかわらず、ユーロスタットは、現代の領土区分を歴史的統計にドッキングするために常に再計算するわけではありません。 実際には、これは、最新バージョンのNUTSでは、領域の境界が変更された場合に、データにかなりの数の省略が含まれることを意味します。 したがって、研究者は、十分に長い期間を調査するために、自分でデータを調和させる必要があります。 もちろん、データ回復プロセスには、過去に存在したことのない地域の統計指標を評価できるように、時には粗雑な仮定が豊富にあります。







さらに、EurostatがNUTSの現在のバージョンのデータのみを公開しているという事実によって作業が複雑になっています(少なくとも、アーカイブされたデータセットをダウンロードする場所を見つけられませんでした。 私は博士課程プロジェクトの一環として、NUTS-2レベルで地域分析を実施しています。 最長の観測期間を確保するために、データの調和に関する主要な作業を行った2015年に、2010年版のNUTSを選択しました。NUTSは 、公式の地域人口統計EUROPOP2013の基礎となりました 。 結果を再現するために、2015年にダウンロードした人口死亡の 年齢構造に関するNUTSレベルのソースデータのコピーをfigshareに投稿しました







デンマーク



一部の国では、NUTS基準に準拠するために、行政区域の大規模な改革を行わなければなりませんでした。 最も重要な変化は、2007年にデンマークで同時に発生し 、271の古い市町村が98の新しい市町村に変わりました( 改革に関する科学記事を参照)。 同じ改革により、デンマークにNUTSが導入されました。98の新しい自治体が11のNUTS-3リージョンに合併し、さらに5つのNUTS-2リージョンに合併しました。 NUTS-1レベルは際立っていません。これはデンマークのような小さな国に典型的です。







私の知る限り、2007年まで公式レベルでデンマークのNUTS地域のデータを復元する試みは行われていません。 2007年までの期間にユーロスタットによって公開されたヨーロッパ地域の典型的な地図は次のようになります(図3)-デンマーク地域の「データなし」。







fig3

図3. 2006年のヨーロッパのNUTS-2地域の平均寿命。 Eurostat インタラクティブデータ探索ツールのスクリーンショット







このデータの欠如は、デンマークのような多くの点で発展した国にとって非常に驚くべきことです。 新旧の市区町村区分システムを組み合わせるのは非常に困難ですが、NUTSの非常に高い階層レベルで市町村のデータを集約することはそれほど難しくありません。 そして、これはまさに私がやったことであり、この投稿で示したいものです。 私は他の誰かが私の前にこれをやったかどうかを見つけるために多くの努力を費やしました-驚いたことに、私はパブリックドメインで何も見つけませんでした。







本質的には、古い市町村(271)を最新のNUTS-3地域(11)に関連付け、NUTS-3レベルでデータを単純に集約することがタスクです。 次に、NUTS-3はNUTS-2に要素的に集約されます。 このようなタスクは、特に市町村の魅力的なデンマークの名前に煩わされるすべての喜びを考慮すると、長い夜を要する可能性があります。 しかし、幸いなことに、私たちはGIS時代に住んでいます。 GISを使用して、古い自治体をNUTS-3地域にほぼ自動的にドッキングしました。 さらに、プロセス全体がコードRで段階的に示されています







データ



デンマークの古い271市町村の年齢構成に関するデータは、 Statistics Denmarkの公式ウェブサイトから取得されています。 システムでは、未登録ユーザーの場合は10Kエントリ、登録後の場合は100Kエントリのみを一度にダウンロードできます。 データの詳細を考えると、データをアップロードするプロセスは非常に退屈な仕事です。 したがって、私の研究のタスクのために、2001年から2006年のデータをダウンロードしました。 必要に応じて、詳細な市町村データが1979年以来利用可能です。 私が2015年にダウンロードしたデータと使用前にフォーマットされたビットはこちらです。







デンマークの古い市町村の境界の空間データを含むファイルを検索するには、かなりの時間がかかりました。 1年半後にこの質問に戻ると、シェープファイルの元のソースを見つけることができませんでした。 しかし、私はここからそれをダウンロードしたと確信しています 。 今日、計画された作業のためにデータが利用できないという訪問通知があります。 ここで使用されるシェープファイルのコピー。







最後に、計画を成功裏に実施するには、NUTS-3地域の国境が必要です。 ここではすべてが簡単です-データはEurostat Webサイト( Eurostat geodata repository )で入手できます 。 使用したアーカイブシェープファイルは、「NUTS_2010_20M_SH.zip」と呼ばれます。 11のデンマーク地域の選択がここにあります







両方のシェープファイルに使用される地理的投影法はESPG-3044であり、これはデンマークの標準です。







最後に、セッションを準備してデータをダウンロードするRコード(コードに関するコメントは翻訳しません)。







# set locale and encoding parameters to read Danish if(Sys.info()['sysname']=="Linux"){ Sys.setlocale("LC_CTYPE", "da_DK.utf8") danish_ecnoding <- "WINDOWS-1252" }else if(Sys.info()['sysname']=="Windows"){ Sys.setlocale("LC_CTYPE", "danish") danish_ecnoding <- "Danish_Denmark.1252" } # load required packages (install first if needed) library(tidyverse) # version: 1.0.0 library(ggthemes) # version: 3.3.0 library(rgdal) # version: 1.2-4 library(rgeos) # version: 0.3-21 library(RColorBrewer) # version: 1.1-2 mypal <- brewer.pal(11, "BrBG") library(fuzzyjoin) # version: 0.1.2 library(viridis) # version: 0.3.4 # load Denmark pop structures for the old municipalities df <- read_csv("https://ikashnitsky.github.io/doc/misc/nuts2-denmark/BEF1A.csv.gz") # create a directory for geodata ifelse(!dir.exists("geodata"), dir.create("geodata"), "Directory already exists") # download, unzip and read Danish NUTS-3 geodata (31KB) url_nuts <- "https://ikashnitsky.github.io/doc/misc/nuts2-denmark/denmark-nuts3-espg3044.tgz" path_nuts <- "geodata/denmark-nuts3-espg3044.tgz" ifelse(!file.exists(path_nuts), download.file(url_nuts, path_nuts, mode="wb"), 'file alredy exists') # If there are problems downloading the data automatically, please download it manually from # https://ikashnitsky.github.io/doc/misc/nuts2-denmark/denmark-nuts3-espg3044.tgz untar(tarfile = path_nuts, exdir = "geodata") sp_nuts3 <- readOGR(dsn = "geodata/.", layer = "denmark-nuts3-espg3044") gd_nuts3 <- fortify(sp_nuts3, region = "NUTS_ID") # to the ggplot format # download, unzip and read Danish old municipal geodata (6.0MB) url_mun <- "https://ikashnitsky.github.io/doc/misc/nuts2-denmark/kommune2006win1252.tgz" path_mun <- "geodata/kommune2006win1252.tgz" ifelse(!file.exists(path_mun), download.file(url_mun, path_mun, mode="wb"), 'file alredy exists') # If there are problems downloading the data automatically, please download it manually from # https://ikashnitsky.github.io/doc/misc/nuts2-denmark/kommune2006utf8.tgz untar(tarfile = path_mun, exdir = "geodata") sp_mun <- readOGR(dsn = "geodata/.", layer = "kommune2006win1252", encoding = danish_ecnoding) gd_mun <- fortify(sp_mun) # coordinates of the municipalities mun_coord <- bind_cols(as.data.frame(coordinates(sp_mun)), sp_mun@data[,1:3]) %>% transmute(long = V1, lat = V2, enhedid, objectid, name = navn)
      
      





空間ドッキング



最初に地図を見てみましょう。







 ggplot()+ geom_polygon(data = gd_nuts3, aes(long, lat, group = group), color = brbg[3], fill = "grey90", size = 1)+ geom_point(data = mun_coord, aes(long, lat), color = brbg[10], size = 1)+ theme_map()
      
      





fig4

図4.デンマークの旧自治体とNUTS-3地域







市町村の境界(水色)は、NUTS-3地域の境界(水色)よりもはるかに詳細であることがわかります。 これは、市町村の重心がNUTS-3の各地域内に収まる限り難しくありません。 これは、最東点を除くすべての重心に当てはまります。 速いgooglezhは、これがクリスチャンソ、中世の要塞と豊かな歴史を持つ小さな島であることを教えてくれます。 この自治体は特別な地位にあり、NUTSシステムには含まれていません 。 さらに操作する場合は、隣接するボーンホルム島に安全に「貼り付ける」ことができます。







どの重心がNUTSのどの領域に該当するかを調べるために、ライブラリsp



の空間交差( over



)の関数を使用しました。 ここで私は、 ロジャー・ビヴァンドに感謝したい。







 # municipality coordinates to Spatial mun_centr <- SpatialPoints(coordinates(sp_mun), proj4string = CRS(proj4string(sp_nuts3))) # spatial intersection with sp::over inter <- bind_cols(mun_coord, over(mun_centr, sp_nuts3[,"NUTS_ID"])) %>% transmute(long, lat, objectid, nuts3 = as.character(NUTS_ID), nuts2 = substr(nuts3, 1, 4))
      
      





空間マッチングが機能するかどうかを確認します。







 ggplot()+ geom_polygon(data = gd_mun, aes(long, lat, group = group), color = brbg[9], fill = "grey90", size = .1)+ geom_polygon(data = gd_nuts3, aes(long, lat, group = group), color = brbg[3], fill = NA, size = 1)+ geom_point(data = inter, aes(long, lat, color = nuts3), size = 1)+ geom_point(data = inter[is.na(inter$nuts3),], aes(long, lat), color = "red", size = 7, pch = 1)+ theme_map(base_size = 15)+ theme(legend.position = c(1, 1), legend.justification = c(1, 1))
      
      





fig5

図5.古い市町村とデンマークのNUTS-3地域間の空間的交差点の確認







悪くない。 しかし、「NA」のカテゴリに分類された自治体がいくつかあります。つまり、NUTSレベルで一致するものが見つかりませんでした。 このようなケースはいくつありますか?







 # how many failed cases do we have sum(is.na(inter$nuts3))
      
      





 ## [1] 3
      
      





 # where the intersection failed inter[is.na(inter$nuts3),]
      
      





 ## long lat objectid nuts3 nuts2 ## 23 892474.0 6147918 46399 <NA> <NA> ## 65 504188.4 6269329 105319 <NA> <NA> ## 195 533446.8 6312770 47071 <NA> <NA>
      
      





わずか3。私はそれらを手動で修正することにしました。







 # fix the three cases manually fixed <- inter fixed[fixed$objectid=="46399", 4:5] <- c("DK014", "DK01") fixed[fixed$objectid=="105319", 4:5] <- c("DK041", "DK04") fixed[fixed$objectid=="47071", 4:5] <- c("DK050", "DK05")
      
      





最終的な視覚的チェック。







 ggplot()+ geom_polygon(data = gd_mun, aes(long, lat, group = group), color = brbg[9], fill = "grey90", size = .1)+ geom_polygon(data = gd_nuts3, aes(long, lat, group = group), color = brbg[3], fill = NA, size = 1)+ geom_point(data = fixed, aes(long, lat, color = nuts3), size = 1)+ theme_map(base_size = 15)+ theme(legend.position = c(1, 1), legend.justification = c(1, 1))
      
      





fig6

図6. 3つの自治体の手動調整後の空間マッチングの再確認







今ではすべてが正常です。







空間データと統計データを組み合わせます(ファジー結合)



次のタスクは、空間データと統計データの簡単な一致ではありませんでした。 私が見つけた地方自治体のシェープファイルには、デンマーク統計局が使用する識別コードが含まれていませんでした。 そのため、デンマークの名前と西ヨーロッパのコーディングのすべての喜びとともに、市町村を名前で空間データセットと統計データセットにドッキングする必要がありました。 2つのデータセットでは、市町村の名前の綴りが少し異なります。 そして、数百の自治体があります。 それはかなり退屈なルーチンのようです。 あった! 'Fuzzy String Matching'は、素晴らしい開発者David Robinsonによって書かれたfuzzyjoin



ライブラリーでの救助とその優れた実装です。







まず、市町村名のデータを単純化して、もちろん両方のデータセットで小文字に変換しました。 さらに、ドッキングの最初の試みでは、文字「å」を代替のスペル「aa」に置き換えることが理にかなっていることが示されました。 空間データセットでは、各自治体の名前から「Kommune」という単語も削除しました。 古い自治体の名前とコードを含むデンマーク統計局から小さなデータセットをダウンロードしました







 # simplify municipalities names mun_geo <- mun_coord %>% transmute(name = sub(x = name, " Kommune", replacement = ""), objectid) %>% mutate(name = gsub(x = tolower(name), "å", "aa")) mun_stat <- read.csv2("https://ikashnitsky.github.io/doc/misc/nuts2-denmark/stat-codes-names.csv", fileEncoding = danish_ecnoding) %>% select(name) %>% separate(name, into = c("code", "name"), sep = " ", extra = "merge") %>% mutate(name = gsub("\\s*\\([^\\)]+\\)", "", x = name)) %>% mutate(name = gsub(x = tolower(name), "å", "aa"))
      
      





ファジー結合を試行しています。







 # first attempt fuz_joined_1 <- regex_left_join(mun_geo, mun_stat, by = "name")
      
      





出力は、271行ではなく278行のデータフレームです。これは、空間データセットの一部の自治体では、統計データセットに複数の一致が見つかったことを意味します。 これらの問題のあるケースを見つけます。







 # identify more that 1 match (7 cases) and select which to drop fuz_joined_1 %>% group_by(objectid) %>% mutate(n = n()) %>% filter(n > 1)
      
      





 ## Source: local data frame [14 x 5] ## Groups: objectid [7] ## ## name.x objectid code name.yn ## <chr> <dbl> <chr> <chr> <int> ## 1 haslev 105112 313 haslev 2 ## 2 haslev 105112 403 hasle 2 ## 3 brønderslev 47003 739 rønde 2 ## 4 brønderslev 47003 805 brønderslev 2 ## 5 hirtshals 47037 817 hals 2 ## 6 hirtshals 47037 819 hirtshals 2 ## 7 rønnede 46378 385 rønnede 2 ## 8 rønnede 46378 407 rønne 2 ## 9 hvidebæk 46268 317 hvidebæk 2 ## 10 hvidebæk 46268 681 videbæk 2 ## 11 ryslinge 46463 477 ryslinge 2 ## 12 ryslinge 46463 737 ry 2 ## 13 aarslev 46494 497 aarslev 2 ## 14 aarslev 46494 861 aars 2
      
      





そのため、7つの市町村について、2つの一致が見つかりました。 ファジー結合の次の反復では、理想的ではない一致を破棄します。







別の問題は、一部の自治体ではまったく一致しなかったことです。







 # show the non-matched cases fuz_joined_1 %>% filter(is.na(code))
      
      





 ## name.x objectid code name.y ## 1 faxe 105120 <NA> <NA> ## 2 nykøbing falsters 46349 <NA> <NA> ## 3 herstederne 46101 <NA> <NA>
      
      





このようなケースは3つしかなかったため、統計データセットの名前に対応するように、空間データセットでそれらを手動で修正しました。 2つのケースでは、スペルの不正確さが原因で矛盾が発生し、別のケースでは、自治体の名前が変更されました







 # correct the 3 non-matching geo names mun_geo_cor <- mun_geo mun_geo_cor[mun_geo_cor$name=="faxe", "name"] <- "fakse" mun_geo_cor[mun_geo_cor$name=="nykøbing falsters", "name"] <- "nykøbing f." mun_geo_cor[mun_geo_cor$name=="herstederne", "name"] <- "albertslund"
      
      





次に、2回目のドッキングを試みます(空間データセットの名前が調整されます)。







 # second attempt fuz_joined_2 <- regex_left_join(mun_geo_cor, mun_stat, by = "name") # drop non-perfect match fuz_joined_2 <- fuz_joined_2 %>% group_by(objectid) %>% mutate(n = n()) %>% ungroup() %>% filter(n < 2 | name.x==name.y) fuz_joined_2 <- fuz_joined_2 %>% transmute(name = name.x, objectid, code)
      
      





パーフェクト。 最後のステップは、NUTSリージョンを「objectid」フィールドで市町村の古い統計コードに添付することです。







 # finally, attach the NUTS info to matched table key <- left_join(fuz_joined_2, fixed, "objectid")
      
      





NUTSレベルでの地方自治体データの集約



これまでの操作により、古い自治体の統計コードと最新のNUTS地域を一致させる、小さいながらも非常に貴重なデータフレームが提供されました。 残っているのは、古いデータを集約することだけです。 そして、「キー」データセットを統計に添付し、NUTS-3およびNUTS-2レベルで集計します。







 # finally, we only need to aggregate the old stat data df_agr <- left_join(key, df, "code") %>% filter(!is.na(name)) %>% gather("year", "value", y2001:y2006) df_nuts3 <- df_agr %>% group_by(year, sex, age, nuts3) %>% summarise(value = sum(value)) %>% ungroup() df_nuts2 <- df_agr %>% group_by(year, sex, age, nuts2) %>% summarise(value = sum(value)) %>% ungroup()
      
      





2001年にデンマークのNUTS-3地域の労働年齢人口(15〜64歳)の割合を計算し、この情報を地図に表示してみましょう。







 # total population in 2001 by NUTS-3 regions tot_01 <- df_nuts3 %>% filter(year=="y2001") %>% group_by(nuts3) %>% summarise(tot = sum(value, na.rm = TRUE)) %>% ungroup() # working-age population in 2001 by NUTS-3 regions working_01 <- df_nuts3 %>% filter(year=="y2001", age %in% paste0("a0", 15:64)) %>% group_by(nuts3) %>% summarise(work = sum(value, na.rm = TRUE)) %>% ungroup() # calculate the shares of working age population sw_01 <- left_join(working_01, tot_01, "nuts3") %>% mutate(sw = work / tot * 100)
      
      





 # map the shares of working age population in 2001 by NUTS-3 regions ggplot()+ geom_polygon(data = gd_nuts3 %>% left_join(sw_01, c("id" = "nuts3")), aes(long, lat, group = group, fill = sw), color = "grey50", size = 1) + scale_fill_viridis()+ theme_map(base_size = 15)+ theme(legend.position = c(1, 1), legend.justification = c(1, 1))
      
      





fig7

図7. 2001年のデンマークのNUTS-3地域における労働年齢人口(15-64)の割合







結果は現実的に見えます-数値は大都市圏と比較的大都市がある地域で高くなっています。











All Articles