LoginSignup
0
4

More than 5 years have passed since last update.

Pythonでe-statにAPIアクセスしてSQLServerに突っ込むときにJSONとPandasで格闘したメモ

Last updated at Posted at 2019-02-09

はじめに

興味深いAPI

[2019] 公開されているAPI一覧まとめ

ジェイソンとデータフレーム

最初に e-stat からデータを取ろうとしたときに、jsonでの取得まではすんなりいったけど、データフレームの入れ方が悪かった(JSONとデータフレームの関係がわかってなかった)ということがあった。

Syntax(構文)

pandas.read_json
Convert a JSON string to pandas object.

構文について
pandas.read_json(path_or_buf=None, orient=None)
引数について
path_or_buf : a valid JSON string or file-like, default: None
orient : string,
'records' : list like [{column -> value}, ... , {column -> value}]
返り値について
result : Series or DataFrame, depending on the value of typ.

JSON試行錯誤

こんな感じに書いて...
import urllib
import urllib.request
import pandas as pd

# Read appId
with open('api_setting/appid.txt', mode='r', encoding='utf-8') as f:
    appId = f.read()

# stat url
url = r'http://api.e-stat.go.jp/rest/2.1/app/json/getStatsData?'

# Query param setting
keys = {
        "appId"            : appId,
        "lang"             : "J" ,
        "statsDataId"      : "0003143513" ,
        "metaGetFlg"       : "Y" ,
        "cntGetFlg"        : "N",
        "sectionHeaderFlg" : "1"
}

# Get json data
query_param = urllib.parse.urlencode(keys)
df = pd.read_json(urllib.request.urlopen(url + query_param).read(), orient='records')

print(df)
Pythonコンソール(こんなふうに出てきたので)
>>> df
                                                     GET_STATS_DATA
PARAMETER         {'LANG': 'J', 'STATS_DATA_ID': '0003143513', '...
RESULT            {'STATUS': 0, 'ERROR_MSG': '正常に終了しました。', 'DATE...
STATISTICAL_DATA  {'RESULT_INF': {'TOTAL_NUMBER': 13142278, 'FRO...
Pythonコンソール(データを取る練習してたんだけど)
------------- データフレームてっぺんから最下層のリテラルを取るぞ! -------------
>>> print(df.columns)
    Index(['GET_STATS_DATA'], dtype='object')

>>> print(df['GET_STATS_DATA'])
    PARAMETER           {'LANG': 'J', 'STATS_DATA_ID': '0003143513', '...
    RESULT              {'STATUS': 0, 'ERROR_MSG': '正常に終了しました。', 'DATE...
    STATISTICAL_DATA    {'RESULT_INF': {'TOTAL_NUMBER': 13142278, 'FRO...

>>> print(df['GET_STATS_DATA']['RESULT'])
    {'STATUS': 0, 'ERROR_MSG': '正常に終了しました。', 'DATE': '2019-02-07T22:35:18.179+09:00'}

# ここまでで、1つの固有値を取得できた
>>> print(df['GET_STATS_DATA']['RESULT']['ERROR_MSG'])
    正常に終了しました。
Pythonコンソール(データフレームのまま、APIデータまでたどり着けない!)
------------- Pandasで集計するぞ! -------------
# あれ、一段下がっただけでシリーズになっちまうのか...まぁそうか
>>> print(type(df['GET_STATS_DATA']))
    <class 'pandas.core.series.Series'>

# ここから下は辞書になっちまうのか...
>>> print(type(df['GET_STATS_DATA']['STATISTICAL_DATA']))
    <class 'dict'>

>>> print(df['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF'].keys())
    dict_keys(['NOTE', 'VALUE'])
    ????

>>> print(df['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF']['VALUE'])
    (・3・)アルェー?

とにかくデータフレームのまま操作したいのにできない。というハマりポイントがあった。ただこれ整理してみると、JSONデータであればデータフレームの入れ物にはスポッと入ってくれる。ただ、jsonの最初には大概ヘッダが入っている。つまり、データフレームに入れる時点で十分に階層を降りていないといけないということがわかった。
https://dev.classmethod.jp/etc/concrete-example-of-json/

JSONデータの例(あーなるほど、たしかにヘッダが入ってるわ...)
 [ 
   { 
     "InstanceId": "i-XXXXXXXX", 
     "ImageId": "ami-YYYYYYYY", 
     "LaunchTime": "2015-05-28T08:30:10.000Z", 
     "Tags": [ 
       { 
         "Value": "portnoydev-emr", 
         "Key": "Name" 
       }, 
       { 
         "Value": "j-ZZZZZZZZZZZZ", 
         "Key": "aws:elasticmapreduce:job-flow-id" 
       }, 
       { 
         "Value": "CORE", 
         "Key": "aws:elasticmapreduce:instance-group-role" 
       } 
     ] 
   }, 
   (略) 
 ] 
Pythonコンソール(なのでデータフレームに入れるのはJSONナマじゃなくて...)
>>> df = pd.read_json(urllib.request.urlopen(url + query_param).read(), orient='records')
Pythonコンソール(こうなんやな!)
>>> df = pd.read_json(urllib.request.urlopen(url + query_param).read(), orient='records')['GET_STATS_DATA']['STATISTICAL_DATA']

>>> print(type(df))
    <class 'dict'>
    (・3・)アルェー?
Pythonコンソール(オライリーを見てみると、辞書をデータフレームに変換してた)
>>> print(type(result['GET_STATS_DATA']['STATISTICAL_DATA']))
    <class 'dict'>

>>> stats = pd.DataFrame(result['GET_STATS_DATA']['STATISTICAL_DATA'])
>>> print(type(stats))
    <class 'pandas.core.frame.DataFrame'>
>>> stats
                      RESULT_INF  ...                                           DATA_INF
    @id                          NaN  ...                                                NaN
    CLASS_OBJ                    NaN  ...                                                NaN
    CYCLE                        NaN  ...                                                NaN
    FROM_NUMBER                  1.0  ...                                                NaN
    GOV_ORG                      NaN  ...                                                NaN
    MAIN_CATEGORY                NaN  ...                                                NaN
    NEXT_KEY                100001.0  ...                                                NaN
    NOTE                         NaN  ...            {'@char': '***', '$': '該当データがない場合を示す。'}
    OPEN_DATE                    NaN  ...                                                NaN
    OVERALL_TOTAL_NUMBER         NaN  ...                                                NaN
    SMALL_AREA                   NaN  ...                                                NaN
    STATISTICS_NAME              NaN  ...                                                NaN
    STATISTICS_NAME_SPEC         NaN  ...                                                NaN
    STAT_NAME                    NaN  ...                                                NaN
    SUB_CATEGORY                 NaN  ...                                                NaN
    SURVEY_DATE                  NaN  ...                                                NaN
    TITLE                        NaN  ...                                                NaN
    TITLE_SPEC                   NaN  ...                                                NaN
    TOTAL_NUMBER          13142278.0  ...                                                NaN
    TO_NUMBER               100000.0  ...                                                NaN
    UPDATED_DATE                 NaN  ...                                                NaN
    VALUE                        NaN  ...  [{'@tab': '1', '@cat01': '0001', '@area': '13A...
    [22 rows x 4 columns]
Pythonコンソール(VALUEまで取りに行けばいいじゃん?...ダメらしい...)
>>> stats = pd.DataFrame(result['GET_STATS_DATA']['STATISTICAL_DATA']['VALUE'])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'VALUE'

たしかにそこにいるはずなんだが...:thinking:
なんかもう昔のゲームやってるみたいだわ、3次元以上の配列って...(この感じわかるひといる?)
image.png

ん?

Pythonコンソール(なるほど?いままでの単純な表形式の見方を発展させないといけないか)
>>> stats.columns
Index(['RESULT_INF', 'TABLE_INF', 'CLASS_INF', 'DATA_INF'], dtype='object')
>>> stats['DATA_INF']
    @id                                                                   NaN
    CLASS_OBJ                                                             NaN
    CYCLE                                                                 NaN
    FROM_NUMBER                                                           NaN
    GOV_ORG                                                               NaN
    MAIN_CATEGORY                                                         NaN
    NEXT_KEY                                                              NaN
    NOTE                              {'@char': '***', '$': '該当データがない場合を示す。'}
    OPEN_DATE                                                             NaN
    OVERALL_TOTAL_NUMBER                                                  NaN
    SMALL_AREA                                                            NaN
    STATISTICS_NAME                                                       NaN
    STATISTICS_NAME_SPEC                                                  NaN
    STAT_NAME                                                             NaN
    SUB_CATEGORY                                                          NaN
    SURVEY_DATE                                                           NaN
    TITLE                                                                 NaN
    TITLE_SPEC                                                            NaN
    TOTAL_NUMBER                                                          NaN
    TO_NUMBER                                                             NaN
    UPDATED_DATE                                                          NaN
    VALUE                   [{'@tab': '1', '@cat01': '0001', '@area': '13A...
    Name: DATA_INF, dtype: object

# これやるとデータがずばーっと出てくるので
>>> stats['DATA_INF']['VALUE']

# 5レコードにおさえる
>>> stats['DATA_INF']['VALUE'][0:5]
[{'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2019000101', '$': '101.2'}, {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2018001212', '$': '101.2'}, {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2018001111', '$': '101.4'}, {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2018001010', '$': '101.7'}, {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2018000909', '$': '101.4'}]
5レコードのJSONを整理する
>>> stats['DATA_INF']['VALUE'][0:5]
[
    {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2019000101', '$': '101.2'}, 
    {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2018001212', '$': '101.2'}, 
    {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2018001111', '$': '101.4'}, 
    {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2018001010', '$': '101.7'}, 
    {'@tab': '1', '@cat01': '0001', '@area': '13A01', '@time': '2018000909', '$': '101.4'}
]
Pythonコンソール(listかぁ...これもういっちょデータフレームにキャストしてみるか)
>>> print(type(stats['DATA_INF']['VALUE']))
    <class 'list'>

# あっ!なんかキタぞ!めっちゃ試行錯誤いるなぁ...JSON...
>>> df = pd.DataFrame(stats['DATA_INF']['VALUE'])
>>> df
               $  @area @cat01 @tab       @time
    0      101.2  13A01   0001    1  2019000101
    1      101.2  13A01   0001    1  2018001212
    2      101.4  13A01   0001    1  2018001111
    3      101.7  13A01   0001    1  2018001010
    4      101.4  13A01   0001    1  2018000909
    ...      ...    ...    ...  ...         ...
    99995  100.9  00049   0003    1  2011000808
    99996  100.8  00049   0003    1  2011000707
    99997   99.6  00049   0003    1  2011000606
    99998   99.9  00049   0003    1  2011000505
    99999   99.4  00049   0003    1  2011000404
    [100000 rows x 5 columns]

>>> df['$']
    0        101.2
    1        101.2
    2        101.4
    3        101.7
    4        101.4
             ...
    99995    100.9
    99996    100.8
    99997     99.6
    99998     99.9
    99999     99.4
    Name: $, Length: 100000, dtype: object

>>> df['@time']
    0        2019000101
    1        2018001212
    2        2018001111
    3        2018001010
    4        2018000909
                ...
    99995    2011000808
    99996    2011000707
    99997    2011000606
    99998    2011000505
    99999    2011000404
    Name: @time, Length: 100000, dtype: object

補足:見てるオライリーはこれ
オライリーって無骨だけど読みやすいよね
image.png

JSONコンソール操作まとめ

res(=Full JSON) が返ってきてから、dfに入れる直前までの間に、単純なタテヨコの一般的な(?)2元配列になるところまで十分にJSON配列(?)を潜ってからデータフレームに渡すんや。...というよりは、dfに代入してデータフレーム手に入ったー!って思ってたのはただのナマJSONだった(pd.DataFrame(...)の漏れ)

Pythonコンソール
>>> import urllib
>>> import urllib.request
>>> import pandas as pd
>>> appId = '****************************'
>>> url = r'http://api.e-stat.go.jp/rest/2.1/app/json/getStatsData?'
>>> keys = {
        "appId"            : appId,
        "lang"             : "J" ,
        "statsDataId"      : "0003143513" ,
        "metaGetFlg"       : "Y" ,
        "cntGetFlg"        : "N",
        "sectionHeaderFlg" : "1"
    }
>>> query_param = urllib.parse.urlencode(keys)
>>> res = pd.read_json(urllib.request.urlopen(url + query_param).read(), orient='records')
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF']['VALUE'])
>>> df
               $  @area @cat01 @tab       @time
    0      101.2  13A01   0001    1  2019000101
    1      101.2  13A01   0001    1  2018001212
    2      101.4  13A01   0001    1  2018001111
    3      101.7  13A01   0001    1  2018001010
    4      101.4  13A01   0001    1  2018000909
    ...      ...    ...    ...  ...         ...
    99995  100.9  00049   0003    1  2011000808
    99996  100.8  00049   0003    1  2011000707
    99997   99.6  00049   0003    1  2011000606
    99998   99.9  00049   0003    1  2011000505
    99999   99.4  00049   0003    1  2011000404
    [100000 rows x 5 columns]

>>> df['$']
    0        101.2
    1        101.2
    2        101.4
    3        101.7
    4        101.4
             ...
    99995    100.9
    99996    100.8
    99997     99.6
    99998     99.9
    99999     99.4
    Name: $, Length: 100000, dtype: object

>>> df['@time']
    0        2019000101
    1        2018001212
    2        2018001111
    3        2018001010
    4        2018000909
                ...
    99995    2011000808
    99996    2011000707
    99997    2011000606
    99998    2011000505
    99999    2011000404
    Name: @time, Length: 100000, dtype: object

image.png
ようやく僕が知ってるデータフレームまできたぁ~:sob:
いっかい寝よ...

つべこべいわずにJSON整形サイトに流し込め!

http://tm-webtools.com/Tools/JsonBeauty
元のデータを知らない(作ってない)んだから試行錯誤になるのはあたりまえ!

あっ!階層一撃で追えるわ...トホホ
{
  "GET_STATS_DATA": {
    "RESULT": {
      "STATUS": 0,
      "ERROR_MSG": "正常に終了しました。",
      "DATE": "2019-02-08T21:16:05.801+09:00"
    },
    "PARAMETER": {
      "LANG": "J",
      "STATS_DATA_ID": "0003143513",
      "DATA_FORMAT": "J",
      "START_POSITION": 1,
      "METAGET_FLG": "Y",
      "CNT_GET_FLG": "N",
      "SECTION_HEADER_FLG": 1
    },
    "STATISTICAL_DATA": {
      "RESULT_INF": {
        "TOTAL_NUMBER": 13142278,
        "FROM_NUMBER": 1,
        "TO_NUMBER": 100000,
        "NEXT_KEY": 100001
      },
      "TABLE_INF": {
        "@id": "0003143513",
        "STAT_NAME": {
          "@code": "00200573",
          "$": "消費者物価指数"
        },
        "GOV_ORG": {
          "@code": "00200",
          "$": "総務省"
        },
        "STATISTICS_NAME": "2015年基準消費者物価指数",
        "TITLE": {
          "@no": "1",
          "$": "消費者物価指数(2015年基準)"
        },
        "CYCLE": "-",
        "SURVEY_DATE": 0,
        "OPEN_DATE": "2019-01-25",
        "SMALL_AREA": 0,
        "MAIN_CATEGORY": {
          "@code": "07",
          "$": "企業・家計・経済"
        },
        "SUB_CATEGORY": {
          "@code": "03",
          "$": "物価"
        },
        "OVERALL_TOTAL_NUMBER": 13142278,
        "UPDATED_DATE": "2019-01-24",
        "STATISTICS_NAME_SPEC": {
          "TABULATION_CATEGORY": "2015年基準消費者物価指数"
        },
        "TITLE_SPEC": {
          "TABLE_NAME": "消費者物価指数(2015年基準)"
        }
      },
      "CLASS_INF": {
        "CLASS_OBJ": [
          {
            "@id": "tab",
            "@name": "表章項目",
            "CLASS": [
              {
                "@code": "1",
                "@name": "指数",
                "@level": ""
              },
              {
                "@code": "2",
                "@name": "前月比・前年比・前年度比",
                "@level": "",
                "@unit": "%"
              },
              {
                "@code": "3",
                "@name": "前年同月比",
                "@level": "",
                "@unit": "%"
              },
              {
                "@code": "4",
                "@name": "ウエイト(実数)",
                "@level": ""
              },
              {
                "@code": "5",
                "@name": "ウエイト(万分比)",
                "@level": ""
              }
            ]
          },
          {
            "@id": "cat01",
            "@name": "2015年基準品目",
            "CLASS": [
              {
                "@code": "0001",
                "@name": "0001 総合",
                "@level": "1"
              },
              {
                "@code": "0002",
                "@name": "0002 食料",
                "@level": "1"
              },
              {
                "@code": "0003",
                "@name": "0003 穀類",
                "@level": "3",
                "@parentCode": "0002"
              },
              (略) 
            ]
          }
        ]
      },
      "DATA_INF": {
        "NOTE": {
          "@char": "***",
          "$": "該当データがない場合を示す。"
        },
        "VALUE": [
          {
            "@tab": "1",
            "@cat01": "0001",
            "@area": "13A01",
            "@time": "2019000101",
            "$": "101.2"
          },
          {
            "@tab": "1",
            "@cat01": "0001",
            "@area": "13A01",
            "@time": "2018001212",
            "$": "101.2"
          },
          {
            "@tab": "1",
            "@cat01": "0001",
            "@area": "13A01",
            "@time": "2018001111",
            "$": "101.4"
          },
          (略) 
        ]
      }
    }
  }
}

e-Stat

(JSONの試行錯誤は上のセクションにまとめたので、それ以外を書きます)

参考ページ

e-Stat(政府統計)のAPI機能をつかってpythonでグラフを書いたりしてみる
APIの使い方 how_to_use

前準備情報等

・ユーザー登録
・ログインしてマイページ→API機能(アプリケーションID発行)
・名称は「開発用」
・URLは「ttp://localhost/」(※Qiitaだとリンクになっちゃうから先頭の h は削除)
消費者物価指数 2015年基準消費者物価指数(公開日:2019-01-25)
image.png

消費者物価指数(URLをよく見ると「appId」に何も入っていない)(使い方を見て、jsonで表示するように加工してある)
http://api.e-stat.go.jp/rest/2.1/app/json/getStatsData?appId=&lang=J&statsDataId=0003143513&metaGetFlg=Y&cntGetFlg=N&sectionHeaderFlg=1

データについて

db形式で表示されるページがあるなら先に構造を見ておけってのは、まぁあたりまえっちゃあたりまえだったね:innocent:
このへんかな?
https://www.e-stat.go.jp/api/api-info/e-stat-manual#api_3_4

うーん?
cdArea 地域事項?
lvCat01 分類事項01?
cdTab 表章事項?
lvTime 時間軸事項?

VALUE 統計数値(セル)の情報です。データ件数分だけ出力されます。
属性として表章事項コード(tab)、分類事項コード(cat01 ~ cat15)、地域事項コード(area)、時間軸事項コード(time)、単位(unit)を保持します。全ての属性はデータがある場合のみ出力されます。

単語:表章 https://www.stat.go.jp/data/kokusei/2010/users-g/pdf/mikata.pdf

うーん、例えば地域コード 13A01 はどこだよ!?マスタは?みたいな感じになるよなぁ~??

Pythonコンソール
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF']['VALUE'])
>>> df
               $  @area @cat01 @tab       @time
    0      101.2  13A01   0001    1  2019000101
    1      101.2  13A01   0001    1  2018001212
    2      101.4  13A01   0001    1  2018001111
    3      101.7  13A01   0001    1  2018001010
    4      101.4  13A01   0001    1  2018000909
    ...      ...    ...    ...  ...         ...
    99995  100.9  00049   0003    1  2011000808
    99996  100.8  00049   0003    1  2011000707
    99997   99.6  00049   0003    1  2011000606
    99998   99.9  00049   0003    1  2011000505
    99999   99.4  00049   0003    1  2011000404
    [100000 rows x 5 columns]
Pythonコンソール(あっ!わかった!同じJSONに入ってるぞ!CLASS_INFだ)
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'])
>>> df
         @id        @name                                              CLASS
    0    tab         表章項目  [{'@code': '1', '@name': '指数', '@level': ''}, ...
    1  cat01    2015年基準品目  [{'@code': '0001', '@name': '0001 総合', '@level...
    2   area  地域(2015年基準)  [{'@code': '13A01', '@name': '13100 東京都区部', '@...
    3   time     時間軸(年・月)  [{'@code': '2019000101', '@name': '2019年1月', '...

整形サイト http://tm-webtools.com/Tools/JsonBeauty でJSONとにらめっこだな。
わかってきたのは...

Pythonコンソール
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][0]['CLASS'])
>>> df
      @code @level         @name @unit
    0     1                   指数   NaN
    1     2         前月比・前年比・前年度比     %
    2     3                前年同月比     %
    3     4             ウエイト(実数)   NaN
    4     5            ウエイト(万分比)   NaN
Pythonコンソール
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][1]['CLASS'])
>>> df
        @code @level                              @name @parentCode
    0    0001      1                            0001 総合         NaN
    1    0002      1                            0002 食料         NaN
    2    0003      3                            0003 穀類        0002
    3    0004      5                            0004 米類        0003
    4    1000      6                          1000 うるち米        0004
    ..    ...    ...                                ...         ...
    793  0905      1  0905 食料(酒類を除く)及びエネルギーを除く総合(季節調整済)         NaN
    794  0921      1                      0921 財(季節調整済)         NaN
    795  0922      1                 0922 半耐久消費財(季節調整済)         NaN
    796  0923      1               0923 生鮮食品を除く財(季節調整済)         NaN
    797  0906      1       0906 生鮮食品及びエネルギーを除く総合(季節調整済)         NaN
[798 rows x 4 columns]
Pythonコンソール
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][2]['CLASS'])
>>> df
        @code @level        @name
    0   13A01      1  13100 東京都区部
    1   00000      1           全国
    2   00011      1     人口5万以上の市
    3   00012      1          大都市
    4   00013      1          中都市
    ..    ...    ...          ...
    67  14A02      1    14130 川崎市
    68  14A03      1   14150 相模原市
    69  22A02      1    22130 浜松市
    70  27A02      1     27140 堺市
    71  40A01      1   40100 北九州市
    [72 rows x 3 columns]
Pythonコンソール
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][3]['CLASS'])
>>> df
              @code @level     @name @parentCode
    0    2019000101      4   2019年1月  2019000103
    1    2018001212      4  2018年12月  2018001012
    2    2018001111      4  2018年11月  2018001012
    3    2018001010      4  2018年10月  2018001012
    4    2018000909      4   2018年9月  2018000709
    ..          ...    ...       ...         ...
    681  1970000404      4   1970年4月  1970000000
    682  1970000303      4   1970年3月  1970000000
    683  1970000202      4   1970年2月  1970000000
    684  1970000101      4   1970年1月  1970000000
    685  1970000000      1     1970年         NaN
    [686 rows x 4 columns]

つまり...どういうことだってばよ

データフレームに SQL:Where を適用する

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.where.html
まぁ4つのマスタはそれぞれ違うデータフレームに受け取るのがわかりやすいとは思うけど

Pythonコンソール(この明細を例にとったときに...)
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF']['VALUE'])
>>> df
               $  @area @cat01 @tab       @time
    0      101.2  13A01   0001    1  2019000101
Pythonコンソール:地域コード
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][2]['CLASS'])
>>> df['@name'].where(df['@code'] == '13A01')[0]
    '13100 東京都区部'
Pythonコンソール:カテゴリー
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][1]['CLASS'])
>>> df['@name'].where(df['@code'] == '0001')[0]
    '0001 総合'
Pythonコンソール:表彰事項
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][0]['CLASS'])
>>> df['@name'].where(df['@code'] == '1')[0]
    '指数'
Pythonコンソール:時間軸(年・月)
>>> df = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][3]['CLASS'])
>>> df['@name'].where(df['@code'] == '2019000101')[0]
    '2019年1月'
つまりソースはこうなるな?
# translation to dataframe from json-data 
data = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['DATA_INF']['VALUE'])
area = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][2]['CLASS'])
category = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][1]['CLASS'])
tab = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][0]['CLASS'])
time = pd.DataFrame(res['GET_STATS_DATA']['STATISTICAL_DATA']['CLASS_INF']['CLASS_OBJ'][3]['CLASS'])

複数のデータフレームをInnerJoinする

If we want to join using the key columns, we need to set key to be the index in both df and other. The joined DataFrame will have key as its index.
キー列を使って結合したい場合は、keyをdfとotherの両方のインデックスになるように設定する必要があります。結合されたDataFrameはそのインデックスとしてキーを持ちます。

公式ドキュメントのお手本
>>> df.set_index('key').join(other.set_index('key'))
          A    B
    key
    K0   A0   B0
    K1   A1   B1
    K2   A2   B2
    K3   A3  NaN
    K4   A4  NaN
    K5   A5  NaN

ここから先は結合するにあたって項目名がカブってくるので列名を変更する。
data以外のデータフレームの「@name」だけでいいんじゃないかな
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.rename.html#pandas.DataFrame.rename

Pythonコンソール:列構成の確認と調整、結合
# 確認:
>>> data.columns
    Index(['$', '@area', '@cat01', '@tab', '@time'], dtype='object')
>>> area.columns
    Index(['@code', '@level', '@name'], dtype='object')
>>> category.columns
    Index(['@code', '@level', '@name', '@parentCode'], dtype='object')
>>> tab.columns
    Index(['@code', '@level', '@name', '@unit'], dtype='object')

# 調整1: 列名変更
>>> area = area.rename(columns={'@name': 'area_name'})
>>> category = category.rename(columns={'@name': 'category_name'})
>>> tab = tab.rename(columns={'@name': 'tab_name'})

# 調整2: くっつけるとカブる列とか不要列の削除(レベルは、「全国」と「都道府県」を分けるみたいな深さ情報みたい)
# エラーを出しながら判断
>>> area = area.drop(columns=['@level'])
>>> category = category.drop(columns=['@level','@parentCode'])
>>> tab = tab.drop(columns=['@level','@unit'])

# 結合: Inner Join
>>> joined = data.set_index('@area').join(area.set_index('@code'))
>>> joined = joined.set_index('@cat01').join(category.set_index('@code'))
>>> joined = joined.set_index('@tab').join(tab.set_index('@code'))

# 確認: たぶんこれ「指数」以外もあるんだよね(「:10」で、10件だけ表示)
>>> joined[:10]
                   $ area_name category_name tab_name time_name
    1970000000  31.5        全国       0001 総合       指数     1970年
    1970000000  31.7  人口5万以上の市       0001 総合       指数     1970年
    1970000000  31.7       大都市       0001 総合       指数     1970年
    1970000000  31.8       中都市       0001 総合       指数     1970年
    1970000000  31.2      小都市A       0001 総合       指数     1970年
    1970000000  33.2     北海道地方       0001 総合       指数     1970年
    1970000000  30.6      東北地方       0001 総合       指数     1970年
    1970000000  31.5      関東地方       0001 総合       指数     1970年
    1970000000  31.5      北陸地方       0001 総合       指数     1970年
    1970000000  31.5      東海地方       0001 総合       指数     1970年

データの加工

これ最初さー、「2019年1月」とか日本語入ってるとあとから取り回し悪くて仕方ないから int で年月項目にしようとしたんだけど13行目と14行目見て?:eyes:
年度!?いらないよー。かんべんしてくださいよこれ。で、@levelって 1 と 4 以外にあるのかな?って思ってみるも、Pythonコンソールってデータを省略して見せてくるので、しかたなくcsv出力をして確認。 1 と 4 しかないことを確認できたので 1 のレコードを消しましょう。そうすれば ym にできるはず。

Pythonコンソール:年どまりとか年度とかが混ざってる
>>> time
             @code @level     @name @parentCode
   0    2019000101      4   2019年1月  2019000103
   1    2018001212      4  2018年12月  2018001012
   2    2018001111      4  2018年11月  2018001012
   3    2018001010      4  2018年10月  2018001012
   4    2018000909      4   2018年9月  2018000709
   5    2018000808      4   2018年8月  2018000709
   6    2018000707      4   2018年7月  2018000709
   7    2018000606      4   2018年6月  2018000406
   8    2018000505      4   2018年5月  2018000406
   9    2018000404      4   2018年4月  2018000406
   10   2018000303      4   2018年3月  2018000103
   11   2018000202      4   2018年2月  2018000103
   12   2018000101      4   2018年1月  2018000103
   13   2018000000      1     2018年         NaN
   14   2017100000      1    2017年度         NaN
   15   2017001212      4  2017年12月  2017001012
   16   2017001111      4  2017年11月  2017001012
   17   2017001010      4  2017年10月  2017001012
main.py:いろいろ加工しました。文字加工のエッセンスが詰まっているね
# edit text to '201901' from '2019年1月'
time['yyyy'] = time['time'].str.split('年', expand=True)[0]
time['mm'] = time['time'].str.split('年', expand=True)[1].str.strip('月').str.zfill(2)
time['yyyymm'] = time['yyyy'] + time['mm']

# If there is 'A' in the middle, it means 'city'
area['city_flag'] = area['@code'].str.contains('..A..')
area['area'] = area['area'].str.split(' ', expand=True)[1]

image.png

API取得制限に注意な

なんかデータ足りなくない??って思って、例えばカテゴリーが「総合」か「食料」か「穀物」までしかないんだよなぁ~、くっつけかた間違えたかなぁ~??とか思ってたけどこれだったか。data の時点のデータフレームでは100,000レコードであることを確認できました。URLをクエリパラメータ作ってひっぱるときにレコード減らす工夫するしかないね。

Q7 : 【統計データ取得機能】10万件を超えるデータを取得できません。
A7 : API機能は、一度に最大で10万件のデータを返却します。そのため、統計データが一度に取得できない場合には、継続データの取得開始位置をレスポンスのタグの値として出力します。継続データを要求する場合は、データ取得開始位置パラメータ(startPosition)にこの値を指定することで、取得できます。

SQLServerでテーブルを作る

テーブル作成クエリ
CREATE TABLE e_Stat (
      yyyymm INT NOT NULL
    , category NVARCHAR(50) NULL
    , area NVARCHAR(50) NULL
    , amount NUMERIC(5,1) NULL
);

プログラムを流してみる

あーできたー... 解散!解散ー!おつかれー
うまいもんくって寝ましょ:relaxed:
image.png

Pythonコンソール:上位30件のみ
basic_connect-api-e_stat> python main.py
197002,0002 食料,岡山市,30.4
197004,0002 食料,福井市,29.6
197006,0002 食料,盛岡市,30.0
197008,0001 総合,山口市,32.9
197010,0001 総合,長野市,32.7
197012,0001 総合,秋田市,32.2
197101,0002 食料,松山市,33.0
197103,0002 食料,名古屋市,33.3
197105,0002 食料,水戸市,31.7
197107,0001 総合,北九州市,32.6
197109,0001 総合,大津市,34.9
197112,0001 総合,奈良市,35.4
197202,0001 総合,横浜市,34.4
197205,0002 食料,松江市,31.1
197209,0002 食料,青森市,32.8
197301,0001 総合,甲府市,35.5
197304,0002 食料,高松市,38.9
197308,0002 食料,福島市,37.7
197312,0001 総合,津市,41.6
197402,0002 食料,富山市,45.1
197402,0002 食料,金沢市,46.3
197402,0002 食料,福井市,44.7
197402,0002 食料,甲府市,46.1
197402,0002 食料,長野市,48.4
197402,0002 食料,岐阜市,45.0
197402,0002 食料,静岡市,44.6
197402,0002 食料,名古屋市,48.0
197402,0002 食料,津市,44.2
197402,0002 食料,大津市,51.6
197402,0002 食料,京都市,49.9

ソースコード

ソースをgithubにしとくとあとがラクねぇ~
https://github.com/duri0214/Python/tree/master/basic_connect-api-e_stat

改修履歴

・1回あたり取得量100,000レコードを突破するべくループを組んで全量取得可能にしました

0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4