ファイルの扱い(後半)

教科書 Lesson 10

教科書 Lesson 10 の続きをやってください。10.2 ですね。

注意

以下に各節ごとに細かく書き出しますので、それに沿って作業してください。なお、Sample.csv ファイルを用意していますが、最初にこれをエディタと、Numbers (あるいは Excel などの表計算ソフト)で開いて見比べてみると良いです。カンマと改行で、表の縦横(セルの関係性)を表現していることがわかるでしょう。

image-20231117160730853

Excelで文字化けが起きた?
もし自分が作成した CSV データを Excel で開いたときにいわゆる「文字化け」を起こした場合、それはBOM情報付きで保存しなかったからです。Excelで開きたいのなら UTF-8 の BOM 付きでエンコードして保存しなおせば良いです。VS Code での操作例などは探せば見つかると思います。例えばこの記事など。

10.2 CSVファイル

CSVファイルを読み込む

教科書の通りに読み進めてください。以下の点が理解できたでしょうか。

そして二重ループによって表の各行および各列の要素が取り出せることがわかったでしょうか。

プログラムファイルの名前を csv.py にしない
CSVを試す小さなプログラムを作るのに、ファイル名を csv.py とするのは自然なことと思いますが、困った事にそれではインポート ( import csv ) 操作ができなくなります。少なくとも Mac 標準の Python 実行環境は、import でモジュール名を指定すると、システムライブラリとして用意されているモジュールより先に、カレントディレクトリにその名前のファイルを探しに行きます。つまりもし csv.py がカレントディレクトリに存在すれば、それが読み込まれてシステムが用意している csv モジュールは読み込まれず、reader 等を使おうと思ったときに AttributeError: module ‘csv’ has no attribute ‘reader’ というエラーが出ます。

CSVファイルに書き込む

ここも教科書の通りに読み進めてください。ただ、今回は読み込みとそのデータ処理に集中したいので、軽く「書けるんだね」くらいの意識で良いです。

それより確認課題に進んでください。手を動かして覚えましょう。

確認課題

これから本物の統計データを CSV 形式で取得し、そこから必要な情報を抽出するプログラムを作ります。具体的な機能は以下の通りです。

実行例を以下に示します。

yasuda@Cecil 13 % echo "札幌市" | python3 csvSelect.py
['札幌市', 138928, 161177, 195487, 227790, 292320, 264910, 255251, 240546, 130088, 34690, 1142, 3]
yasuda@Cecil 13 % 

以下に作業手順を説明します。

  1. e-stat からのデータ取得
  2. ファイル内容の確認
  3. ファイルのオープン・読み出し・行の探索
  4. 機能追加(前半)特定の市のデータを抜き出す
  5. 機能追加(後半)10歳区分の年齢データに変換する

これ以降に詳細を示しますから、それに沿って作業を進めてください。

1. e-stat からのデータ取得

政府が提供している統計データの多くは e-stat (https://www.e-stat.go.jp) から取得することが可能です。e-stat にアクセスして令和2年度国勢調査の人口基本集計の表 2-1-1をダウンロードして下さい。

image-20231116233720546

image-20231116233817595

これで CSV ファイルがダウンロードされます。ファイル名は「FEH_00200521_〜〜〜〜.csv」といった名前になっているでしょう。

BOM付き?
BOMは UTF エンコードされたテキストデータの先頭に付く特殊な文字のことです。これがないとある種のソフトウェア(代表的なのは Microsoft の Excel)は文字のデコード(解釈)に失敗して、いわゆる「文字化け」を起こしてしまいます。今回は Python で扱うために BOM は不要(むしろ邪魔)ですが、Excel でこのデータを読みたい人は「BOM 付き」を選択してください。

2. ファイル内容の確認

取得したファイルをNumbers か Excel で開いて、その中身を確認しましょう。

image-20231117001746956

エディタで開くなどして実際のデータがどのようなものか確認することも重要です。

image-20231117161329236

あるいは Unix コマンドレベルでも確認できます。以下は head コマンドの例。

yasuda@Cecil 13 % head -10 FEH_00200521_231112205127.csv
"統計名:","令和2年国勢調査 人口等基本集計 (主な内容:男女・年齢・配偶関係,世帯の構成,住居の状態,母子・父子世帯,国籍など)"
"表番号:","2-1-1"
"表題:","[総人口・総世帯数・男女・年齢・配偶関係] 男女,年齢(各歳),国籍総数か日本人別人口-全国,都道府県,21大都市,特別区,人口50万以上の市"
"実施年月:","2020年","10月"
"市区町村時点(年月日):","-"

"***","当該数値がないもの"
"-","当該数値がないもの"

"","","","","","","","","/表章事項 コード","2020_01","2020_01","2020_01",(以下大量なので略)

すると、この表のデータ形式が以下のような配置になっていることが分かるでしょう。このうち、今回欲しいのは緑の領域だけ、です。つまり「札幌市」の「国籍問わず」の「総数」行のうち、一歳ごとの年齢区分別人数です。

image-20231117003311150

このデータをプログラムで読み出し、必要な行を見つけだし、必要なカラム(列)のデータを取りだすことを目指します。

文字要素をダブルクォーテーションで囲むか、囲まないか
ところでこの CSV データは
"2020000000","2020年","0","国籍総数","0","総数","01100","札幌市","","1,973,395"….
となっていて、つまり全ての要素にダブルクォーテーション(二重引用符)がついています。
多くのCSVデータは、 Sample.csv ファイルのように項目データは「東京」とだけ書かれていて、「 "東京" 」のようにダブルクォーテーションは付いていません。csv モジュールはこの辺りを都合よく解釈して読んでくれますが、データを扱うときはそれで問題ないか、読み込みの際にデータに対して誤った調整をしていないか確認しながら使うのが良いです。

3. ファイルのオープン・読み出し

以下に当該ファイルをオープンして、読み出すプログラムを示します。

import csv
# ファイルを開いて reader を準備
f = open("FEH_00200521_231112205127.csv", "r")
rd = csv.reader(f)
num = 0
for row in rd:
    num += 1 # 行番号を加算
    if num == 20:
        break    # 全部出すと大変なので20行目で途中終了させる
    print(num, row[0:10]) # 最大10番目のフィールドまで出して確認
f.close()

機能的には以下のことを実現しています。コードからそれが読み取れますか?

実行結果を以下に示します。最も左の列は行番号(何行目かを示す)です。13行めまではラベルで、14行めからデータが始まっています。最も右の列、例えば「札幌市」の行にある「1,973,395」は「総数(その市の総人口)」です。

yasuda@Slack 14 % python3 csvSelect.py
1 ['統計名:', '令和2年国勢調査 人口等基本集計\u3000(主な内容:男女・年齢・配偶関係,世帯の構成,住居の状態,母子・父子世帯,国籍など)']
2 ['表番号:', '2-1-1']
3 ['表題:', '[総人口・総世帯数・男女・年齢・配偶関係] 男女,年齢(各歳),国籍総数か日本人別人口-全国,都道府県,21大都市,特別区,人口50万以上の市']
4 ['実施年月:', '2020年', '10月']
5 ['市区町村時点(年月日):', '-']
6 []
7 ['***', '当該数値がないもの']
8 ['-', '当該数値がないもの']
9 []
10 ['', '', '', '', '', '', '', '', '/表章事項 コード', '2020_01']
11 ['', '', '', '', '', '', '', '', '/表章事項', '人口【人】']
12 ['', '', '', '', '', '', '', '', '/年齢 コード', '000']
13 ['時間軸(年次) コード', '時間軸(年次)', '国籍総数か日本人 コード', '国籍総数か日本人', '男女 コード', '男女', '全国~人口50万以上の市 コード', '全国~人口50万以上の市', '/年齢', '総数']
14 ['2020000000', '2020年', '0', '国籍総数', '0', '総数', '00000', '全国', '', '126,146,099']
15 ['2020000000', '2020年', '0', '国籍総数', '0', '総数', '01000', '北海道', '', '5,224,614']
16 ['2020000000', '2020年', '0', '国籍総数', '0', '総数', '01100', '札幌市', '', '1,973,395']
17 ['2020000000', '2020年', '0', '国籍総数', '0', '総数', '02000', '青森県', '', '1,237,984']
18 ['2020000000', '2020年', '0', '国籍総数', '0', '総数', '03000', '岩手県', '', '1,210,534']
19 ['2020000000', '2020年', '0', '国籍総数', '0', '総数', '04000', '宮城県', '', '2,301,996']
yasuda@Slack 14 % 

参考として、データの一部を以下に示します。16行目の「札幌市」の「総数」の人口が 1,973,395 人であることが確認できるでしょう。

image-20231117004600522

4. 機能追加(前半)特定の市のデータを抜き出す

以下の機能を追加してください。

実行すると以下のようになるよう、調整してください。

yasuda@Slack 14 % python3 csvSelect.py
16 ['2020000000', '2020年', '0', '国籍総数', '0', '総数', '01100', '札幌市', '', '1,973,395']
115 ['2020000000', '2020年', '0', '国籍総数', '1', '男', '01100', '札幌市', '', '918,682']
214 ['2020000000', '2020年', '0', '国籍総数', '2', '女', '01100', '札幌市', '', '1,054,713']
313 ['2020000000', '2020年', '1', 'うち日本人', '0', '総数', '01100', '札幌市', '', '1,933,094']
412 ['2020000000', '2020年', '1', 'うち日本人', '1', '男', '01100', '札幌市', '', '897,727']
511 ['2020000000', '2020年', '1', 'うち日本人', '2', '女', '01100', '札幌市', '', '1,035,367']
yasuda@Slack 14 % 

すると「札幌市」のデータが6行もあることがわかるでしょう。京都市などにしても同じです。各行の意味的な違いは「国籍を問わない、日本人のみ」かあるいは「男女を問わない、男、女」かです。次のステップではこのうち「国籍総数」かつ「総数(男女を問わない)」である一行だけを処理の対象とします。

フィルタコマンドによる前処理
今回は処理プログラム自身がコード記述によって「不要な部分を処理しない」ように作っています。しかし前回やったようにフィルタコマンドによる前処理、つまり処理プログラムの「前処理」として不要部分を取り除くことも可能と思います。試してみるのは構いません(というより腕試しにやってみる方が良いです)が、今回の課題としては不要な部分を処理しないためのコードを正しく書けることも評価の対象とします。

5. 機能追加(後半)10歳区分の年齢データに変換する

以下の機能を追加して、プログラムを完成させてください。

yasuda@Cecil 13 % echo "札幌市" | python3 csvSelect.py
['札幌市', 138928, 161177, 195487, 227790, 292320, 264910, 255251, 240546, 130088, 34690, 1142, 3]
yasuda@Slack 14 % 
yasuda@Cecil 13 % echo "札幌" | python3 csvSelect.py  
#### ERROR#### 
   札幌   is not exist.
yasuda@Cecil 13 % 
注意点

動作確認

たとえば京都市を指定して実行すると以下のようになります。

yasuda@Cecil 13 % echo "京都市" | python3 csvSelect.py
['京都市', 98880, 121336, 168127, 152843, 200129, 183958, 155506, 184941, 102482, 25235, 1020, 0]
yasuda@Cecil 13 % 

最後の要素(110歳以上)は数値ではなく “-” ハイフン記号でしたが、うまくゼロになっていますね。