課題
この課題では,2020年の国勢調査の人口速報集計を一部抜粋したファイルから, 各市区町村のデータをすべて読みだして, それらのデータを都道府県別および全国で集計した結果を格納したファイルを生成することにします.
プログラムではデータファイル(入力ファイル)を読み込んで, 集計結果のファイル(出力ファイル)を生成するわけです.
入力ファイル: 国勢調査データファイル(市区町村別) # PandAで提供 出力ファイル: 都道府県別・全国の集計データファイル # 課題のプログラムで生成
「国勢調査データファイル」「都道府県別・全国の集計データファイル」の構成については,以下で説明します.
目次
国勢調査データファイル
集計に用いる「国勢調査データファイル」はPandAからダウンロードしてください(2020_population_census_mini.csv).
リソース > 02_データファイル > 2020_population_census_mini.csv
このファイルには国勢調査(2020年)の「人口速報集計」のデータの一部を抽出して格納しています. ファイルに格納している各市区町村のデータ項目は次のリスト1のとおりです.
- 市区町村コード(各自治体を識別する番号)
- 市区町村名
- 男性人口
- 女性人口
- 5年間の人口増減数(2015年の国勢調査との人口の差;男女の区別なし)
- 面積【km2】
ファイルはCSV形式で記述されています(詳細は以下で説明します). 次のようにして仮想型端末上で開いてみることができます.
$ /usr/bin/localc 2020_population_census_mini.csv &
最初にファイルのプレビューウインドウが表示されます. そのまま[OK]ボタンを押せば,ファイルが開かれるはずです.
CSVファイルを開く別の方法として,ファイルブラウザ([Activities]→[Files])から,CSVファイルをダブルクリックで選択してもOKです.
市区町村コード
「国勢調査データファイル」の「市区町村コード」は次のように構成されています.
- 4桁あるいは5桁の番号
- 最初の1桁あるいは2桁=都道府県の番号(1-47)
# 都道府県番号: 北海道〜栃木県の番号は1桁(1〜9),群馬県〜沖縄県の番号は2桁(10〜47). - 残りの3桁=都道府県内での市区町村の番号
欠損値
国勢調査データファイルには欠損値が一部に含まれています. 欠損値とは「データが存在しないこと」を示すデータです(Rubyで言えばnilに相当します). 今回の課題では欠損値を気にする必要はありません.
課題では,CSVファイルのC列以降のデータは数値に変換して利用します. 国勢調査データファイルでは欠損値は「-」で表現されています. Rubyでは「-」を数値に変換すると「0」か「0.0」になります. 今回は欠損値をこのように変換して「0」として扱うことにして,気にしないことにします.
s = '-' # 欠損値を表す記号(文字列リテラル) x = s.to_i # x == 0 y = s.to_f # y == 0.0
【参考】国勢調査データの入手について
オリジナルの「国勢調査の人口速報集計」は,次から入手できます.
- e-Stat 統計で見る日本
TOP > 分野 > 人口・世帯 > 国勢調査 > 令和2年国勢調査 > データベース > 人口速報集計(男女別人口及び世帯総数) > DB(クロス集計表形式)
都道府県別・全国の集計データファイル
この課題では,上で説明した「国勢調査データファイル」の市区町村のデータに基づいて, 都道府県別,またさらに全国でデータを集計したファイルを生成します. 集計結果のファイルには,元の「国勢調査データファイル」に項目を追加して, 各都道府県および全国について,次のデータを格納することにします.
- 都道府県番号 (追加項目:全国=0,北海道=1,青森県=2,...,鹿児島県=46,沖縄県=47)
- 都道府県名 (追加項目: 全国,北海道,青森県,...,鹿児島県,沖縄県)
- 総人口 (追加項目: 男女の人口の合計)
- 男性人口
- 女性人口
- 5年間の人口増減数(2015年の国勢調査との人口の差;男女の区別なし)
- 面積【km2】
- 人口密度【人/km2】 (追加項目)
各都道府県の人口と面積は,その都道府県の市区町村のデータを合計して算出します. 全国の人口と面積はすべての市区町村のデータの合計から算出します. 上に示したように人口密度も算出します. なお面積と人口密度のデータは小数点以下第1位までを記録することにして, 小数点以下第2位を四捨五入することにします.
CSVファイル
今回利用する国勢調査データファイル(2020_population_census_mini.csv)には,CSV形式でデータが保存されています.
CSV(Comma-Separated Values)は,表データのためのファイル形式の一つです. 「表」は縦横に並んだ「セル」で構成されています. 「表」において,セルの横一列を「行」,セルの縦一列を「列」といいます.
CSVファイルでは,1行ごとに, 各列のデータが「区切り文字」で区切られて並べられています. (フォーマットの名前が示すとおり)標準的な区切り文字はカンマ(,)です. 「スペース」「タブ」などが区切り文字として使われることもあります.
CSVファイルは,一般にはExcelなど表計算ソフトで開きます. しかしデータは通常のテキスト(文字)ですので,Emacsなどのエディタで開くこともできます. エディタで開けば,ファイルのデータそのものを見ることができます. なおCSV内部のデータはすべて「文字」であることに注意してください. 数字が並んでいたとしても,それは数字の列であって,数値ではありません.
データファイルおよび生成するファイルの構成
国勢調査データファイル(2020_population_census_mini.csv)は,次のように構成されています.
- 先頭行=項目名の一覧(リスト1のA〜F)
- 2番目以降の行=各市区町村のデータの一覧
項目の区切り文字は標準の「,」(comma)です.
国勢調査データファイルから生成する「都道府県別および全国」の集計データファイルも,同様の形式でCSVで生成してください.
- 先頭行=項目名の一覧(リスト2のA〜H)
- 2番目の行=全国(番号:0)の集計データの一覧
- 3番目以降の行=各都道府県(番号:1-47)のデータの一覧
項目の区切り文字は標準の「,」(comma)とします.
プログラムテンプレート
次に示すプログラムのテンプレート(雛型)を適宜名前を変えて使って下さい.
このテンプレートでは,集計処理で利用するデータを定義しています. 詳細はテンプレートで確認してください.
実行方法
今回のプログラムは次のように実行することを想定しています(プログラムのファイル名をcensus_aggregate.rbとします).
$ ruby census_aggregate.rb towns.csv prefs.csv
この場合「towns.csv」を読み込んでデータを集計して「prefs.csv」を生成するようにします. 「towns.csv」が市区町村データで構成される国勢調査データファイル, 「prefs.csv」が都道府県+全国の集計データファイルを表しています.
技術要素
プログラム作成に用いる可能性のある技術要素を示します.
- 文字列データの表記
- 文字列の出力(print,puts)
- 文字列の長さ(length,size)
- 部分文字列
- 文字列の数値への変換(to_i,to_f)
- 文字列の分解(split)
- 配列の要素を結合した文字列の生成(join)
- 配列の先頭から要素を取り出す(shift)
- 配列の先頭に要素を追加する(unshift)
- 配列の末尾に要素を追加する(push)
- 配列の末尾の要素を取り出す(pop)
- 配列の負のインデクス
- 部分配列
- 配列を連結する(+)
- 各要素を変換した配列の生成(map)
- 配列の生成
- 2次元配列(配列の配列)の生成
- 指定した桁での四捨五入(round)
- 文字列データの表記
Rubyでは,文字列は,ダブルクォートで括って表記する("...")ことも,シングルクォートで括って表記する('...')こともできる. ダブルクォートで括った文字列では,文字列中に「#{式}」を記述すると, それが「式」の値に置き換えられる(式展開). またダブルクォートで括った文字列では,「\n」(改行文字) 「\t」(タブ)などの「バックスラッシュ記法」で文字を表記できる. 一方でシングルクォートで括った文字列では,(一部の例外を除いて)括られた文字列そのものを表す.m = 0; n = 1 s0 = "m=#{m}\tn=#{n}\n" # ダブルクォート文字列 s1 = 'm=#{m}\tn=#{n}\n' # シングルクォート文字列 print s0 # ==> 「m=0 n=1」と表示し,改行する print s1 # ==> 「m=#{m}\tn=#{n}\n」と表示し,改行しない
文字列をそのまま扱うときはシングルクォートを使うとよい. - 文字列の出力(print,puts)
print,putsによって文字列をファイルに出力できる.message = '"Hello, World!"' # ダブルクォートも含んだ文字列 File.open('foo.txt','w') do |fout| # 「foo.txt」を書き込み用に開く fout.puts(message) # ==> foutにmessageと「改行」を書き出す fout.print(message) # ==> foutにmessageを書き出す end # この時点でファイルを閉じる
putsは文字列を書き出して,さらに改行を書き出す(ただし文字列の末尾が改行の場合は改行は追加されない). printの場合,引数の末尾に改行文字がなければ,改行しないことに注意. - 文字列の長さ(length,size)
lengthメソッド,またはsizeメソッドで,文字列の長さ(文字列を構成する文字の個数)が得られる. length,sizeはどちらを使ってもよい. 英語以外の文字列の文字数も数えられる.str0 = 'This is a string' len0 = str0.size # len0 == 16 str1 = 'これは文字列の例です' len1 = str1.size # len1 == 10
- 部分文字列
文字列の一部を取り出すことができる.str = 'This is a string' # [開始番号..終了番号]で指定 # (負の番号によって末尾から-1,-2,...と逆順で数えた文字を指定できる) str[0..2] # ==> "Thi" str[1..3] # ==> "his" str[2..-1] # ==> "is is a string" str[3..-2] # ==> "s is a strin" # [開始番号,文字数]で指定 str[0,2] # ==> "Th" str[1,4] # ==> "his " str[2,4] # ==> "is i"
- 文字列の数値への変換(to_i,to_f)
文字列にto_iを適用すると,文字列の先頭部分(最初に±があれば,それも合わせて)の数字の列が整数に変換され, to_fを適用すると,(小数点があればその後の数字列も含めて)小数点数に変換される.str0 = '1.2' a = str0.to_i # a == 1 x = str0.to_f # x == 1.2 str1 = '+12.3+4' b = str1.to_i # b == 12 y = str1.to_f # y == 12.3 str2 = '-12.3.4' c = str2.to_i # c == -12 z = str2.to_f # z == -12.3
「to_i」は文字列の先頭から数字列(数字のみが並んでいる部分)を取り出して, 整数に変換する.先頭に数字列がない場合は0に変換する. 「to_f」は文字列の先頭から「数字列」または「数字列.数字列」を取り出して 小数点数に変換する.先頭に数字列がない場合は0.0に変換する. いずれも文字列の先頭が正負の符号(±)の場合はそれも含めて変換する. - 文字列の分解(split)
splitメソッドにより,引数に指定した文字列によって文字列を区切って,区切られた文字列のそれぞれを要素とする配列を生成できる.生成される配列の各要素はすべて文字列であることに注意せよ.str = "204,60,96" # 「,」で区切る s_ary0 = str.split(',') # s_ary0 == ["204","60","96"] # 「0」で区切る s_ary1 = str.split('0') # s_ary1 == ["2","4,6",",96"]
- 配列の要素を結合した文字列の生成(join)
joinメソッドにより,配列の要素をそれぞれ文字列に変換した上で, メソッドの引数に指定した文字列を間に挟んで,(文字列に変換した)要素をすべて結合した文字列を生成する.ary = [1,2,3,4] s0 = ary.join(' ') # s0 == "1 2 3 4" s1 = ary.join(',') # s1 == "1,2,3,4"
- 配列の先頭から要素を取り出す(shift)
shiftメソッドで配列から先頭の要素を取り出せる. 取り出した先頭の要素は配列から削除される. 結果として,他の要素は一つずつ前にずれることになる.a = [2,3,4,5,6] len = a.size # len == 5 b = a.shift # b == 2, a == [3,4,5,6] len2 = a.size # len2 == 4 (先頭が取り除かれた→要素が1個減った)
- 配列の先頭に要素を追加する(unshift)
unshiftメソッドで配列の先頭に要素を追加できる.a = [2,3,4,5,6] a.unshift(1) # a == [1,2,3,4,5,6] len = a.size # len == 6
- 配列の末尾に要素を追加する(push)
pushメソッドで配列の末尾に要素を追加できる.a = [] a.push(2) # a == [2] a.push(3.4) # a == [2, 3.4] a.push(true) # a == [2, 3.4, true]
- 配列の末尾の要素を取り出す(pop)
popメソッドで配列から末尾の要素を取り出せる. 取り出した先頭の要素は配列から削除される.a = [2, 3.4, true] b = a.pop # b == true, a == [2, 3.4] c = a.pop # c == 3.4, a == [2] a.pop # a == []
- 配列の負のインデクス
配列のインデクスとして負の整数を指定することで, 末尾からの位置で要素を参照できる.a = [2,3,5,7,11] a[-1] # ==> 11 a[-2] # ==> 7 a[-3] # ==> 5
- 部分配列
配列の一部を取り出すことができる.ary = ['This','is','an','array','which','has','8','items'] # [開始番号..終了番号]で指定 # (負の番号によって末尾から-1,-2,...と逆順で数えた要素を指定できる) ary[0..2] # ==> ["This","is","an"] ary[1..3] # ==> ["is","an","array"] ary[2..-1] # ==> ["an","array","which","has","8","items"] ary[3..-2] # ==> ["array","which","has","8"] # [開始番号,個数]で指定 ary[0,3] # ==> ["This","is","an"] ary[1,4] # ==> ["is","an","array","which"] ary[2,4] # ==> ["an","array","which","has"]
- 配列を連結する(+)
+演算子によって,配列同士を連結できる.a = [1,2,3] b = [4,5] c = a + b # c == [1,2,3,4,5] d = [1,1,0] c = c + d # c == [1,2,3,4,5,1,1,0]
- 各要素を変換した配列の生成(map)
mapメソッドにより,配列の各要素に同一の変換処理を施して,その結果を新しい配列に格納できる. 各要素に適用する変換はブロックで表現する. 要素をブロックパラメタで表して,変換方法をブロック内に記述する.src0 = [3, -5, 2, 4, -1] dst00 = src0.map { |u| u+1 } # それぞれに1を加える # dst00 == [4, -4, 3, 5, 0] dst01 = src0.map { |v| v.abs } # それぞれを絶対値に変換 # dst01 == [3, 5, 2, 4, 1] src1 = ['a','b','c'] dst10 = src1.map { |s| s.upcase } # それぞれ大文字に変換 # dst10 == ["A","B","C"] dst11 = src1.map { |t| t.succ } # それぞれ「次の文字」に変換 # dst11 == ["b","c","d"]
- 配列の生成
次のようにして,配列を生成して, すべての要素の値を一斉に初期化できる. またブロックを使って,各要素にそれぞれ個別に値を設定することもできる.n = 4 ary0 = Array.new(n,0) # ary0 == [0,0,0,0] ary1 = Array.new(n) { |i| 2**i } # ary1 == [1,2,4,8]
- 2次元配列(配列の配列)の生成
次のようにして,2次元配列(配列を要素とする配列)を生成して, 要素を一斉に初期化できる.m,n = 2,3 # m = 2; n = 3 ary = Array.new(m) { Array.new(n,0) } # ary == [[0,0,0],[0,0,0]] ary[0][0] += 1 # ary == [[1,0,0],[0,0,0]] # [*注意*] # 文法上は次のように初期化することもできる. # しかし上の場合とは異なる状態になる. # (一つの「n要素の配列」がm回繰り返し参照されている配列を作る) # foo = Array.new(m,Array.new(n,0)) # foo == [[0,0,0],[0,0,0]] foo[0][0] += 1 # foo == [[1,0,0],[1,0,0]]
- 指定した桁での四捨五入(round)
roundメソッドによって,指定した桁までで四捨五入した値が得られる. 桁数を指定しない場合は,四捨五入して整数に変換する.x = 1.2345 y = x.round # y == 1 (整数) z = x.round(1) # z == 1.2 w = x.round(3) # w == 1.235 x.round.to_f # ==> 1.0