データの読み書き

データの読み込み

Python でデータを読み込むときは pandas(パンダズ)というライブラリを使います。

CSV ファイルの読み込み

ファイル名または URL を指定して CSV ファイルを読み込みます:

import pandas as pd

df = pd.read_csv("filename.csv")
df.head()   # 頭の5行だけ表示

読み込んだデータは DataFrame オブジェクトです:

In [ ]: type(df)
Out[ ]: pandas.core.frame.DataFrame

read_csv() でよく使うオプション:

デフォルトでは次の文字列が欠測値(NaN)と判断されます:

'#N/A', '#N/A N/A', '#NA', '-1.#IND', '-1.#QNAN', '-NaN', '-nan',
'1.#IND', '1.#QNAN', 'N/A', 'NA', 'NULL', 'NaN', 'n/a', 'nan', 'null'

これ以外が必要なときは na_valueskeep_default_na(デフォルトは True)で指定できます。空欄だけを欠測値としたい場合は次のようにします:

df = pd.read_csv("finename.csv", na_values="", keep_default_na=False)

10進で切りの良い値なのに微妙な誤差が入ることがあります。例:

from io import StringIO
data = ("A\n1258.477\n")
df = pd.read_csv(StringIO(data))
df.A[0] - 1258.477    # -2.2737367544323206e-13

これを避けるには float_precision="high" または float_precision="round_trip" というオプションを与えます。

Excel ファイルの読み込み

Excel ファイル(xls,xlsx)を読むには xlrd というライブラリが必要です。別途 conda install xlrd または pip install xlrd などとしてインストールしておきます。read_excel() を使うと自動的に import されます。

df = pd.read_excel("filename.xlsx")

read_csv()read_excel() などの詳細は pandas の IO Tools のページ,あるいは IPython で ?pd.read_csv と打ち込むと出てくるヘルプをご覧ください。

DataFrame の操作

pandas の DataFrame は R のデータフレームとほぼ同じです。簡単な解説が 10 Minutes to pandas にあります。

df          # 全体を表示
df.head()   # 頭の5行だけ表示
df.tail()   # 末尾の5行だけ表示
df.columns  # 各列の名前
df.dtypes   # 各列の名前とデータ型を表示
df.index    # 行の名前(デフォルトでは 0 から始まる行番号)
df.describe() # 記述統計量を表示
df1 = df.dropna() # 欠測値のある行を外す
df2 = df.fillna(0) # 欠測値を0で置き換える
df2 = df.fillna(method="ffill") # 欠測値を直前の行で置き換える

データの(0から数えて)例えば2行目から4行目を選択するには df[2:5] のようにします。データの一部の列を選択するには df.列名 あるいは df['列名'] あるいは df[['列名1','列名2']] のようにします。行列のように行名・列名で選ぶには df.loc['行名','列名'],行番号・列番号で選ぶには df.iloc[行番号,列番号] とします。複数与えるには [2,3,4] のようにリストにするか,2:5 のように範囲で表します(Pythonでは番号は0から数え,2:5 のような範囲は最後の5を含まないことに注意)。

df['列名'] はSeries(1列だけのDataFrame)という型のものです。普通のベクトル(ndarray)に直すには df['列名'].values とします。

df['列名'].value_counts(sort=False)  # 列の度数分布表
df['列名'].mean()  # 列の平均(median(), var(), std() なども同様)
df.groupby('列名1')['列名2'].mean() # 列名1でグループ化した列名2の平均
pd.crosstab(df['列名1'], df['列名2']) # クロス集計

データ型の指定

この節については,いしおさんのブログDataFrameのメモリサイズを節約するが参考になります。

例えば shinjuku.csv というCSVファイルを読み込むとしましょう。

import matplotlib.pyplot as plt
import pandas as pd
import sys

url = "https://oku.edu.mie-u.ac.jp/~okumura/stat/data/shinjuku.csv"
df1 = pd.read_csv(url)
df1.shape           # (25705, 4)
sys.getsizeof(df1)  # 2493489

df1.head() の結果は次のようになります:

           datetime     max     min     avg
0  2011/03/01 00:00  0.0370  0.0316  0.0338
1  2011/03/01 01:00  0.0368  0.0324  0.0342
2  2011/03/01 02:00  0.0357  0.0325  0.0340
3  2011/03/01 03:00  0.0371  0.0322  0.0343
4  2011/03/01 04:00  0.0363  0.0321  0.0342

まず,日付はインデックスに移動してみましょう:

df2 = pd.read_csv(url, index_col='datetime')
df2.shape           # (25705, 3)
sys.getsizeof(df2)  # 2493409

サイズの節約にはなりませんでした。df2.head() は次のようになります:

                     max     min     avg
datetime                                
2011/03/01 00:00  0.0370  0.0316  0.0338
2011/03/01 01:00  0.0368  0.0324  0.0342
2011/03/01 02:00  0.0357  0.0325  0.0340
2011/03/01 03:00  0.0371  0.0322  0.0343
2011/03/01 04:00  0.0363  0.0321  0.0342

日付が文字列で読み込まれてしまうのがサイズを大きくする原因です。日時データは parse_dates に指定して,pandasの64ビットの「Timestamp」型にします。この型は1970年元旦を起点としたナノ秒単位の整数で,1677年から2262年まで使えます(pd.Timestamp.minpd.Timestamp.max)。残りの列は特に何もする必要はないのですが,ここではメモリが逼迫していると仮定して,32ビットの浮動小数点型にしてみます:

df3 = pd.read_csv(url, index_col='datetime', parse_dates=['datetime'],
                  dtype={'max':'float32', 'min':'float32', 'avg':'float32'})
sys.getsizeof(df3)  # 514124

列指定は番号でもかまいません:

df4 = pd.read_csv(url, index_col=0, parse_dates=[0],
                  dtype={1:'float32', 2:'float32', 3:'float32'})
sys.getsizeof(df4)  # 514124

インデックス以外の列指定が同じなら,次のようにしてもかまいません:

df5 = pd.read_csv(url, index_col=0, parse_dates=[0], dtype='float32')
sys.getsizeof(df5)  # 514124

データ型としては,符号付き整数 'int8''int16''int32''int64',符号なし整数 'uint8''uint16''uint32''uint64',浮動小数点型 'float16''float32''float64''float128',論理型 'bool'True または False)などが使えるはずです。次のような簡単なプログラムで確認できます:

from io import StringIO
data = ("A,B\n1,1.5\n2,2.5")
df = pd.read_csv(StringIO(data), dtype={0:'int8', 1:'float16'})
sys.getsizeof(df)
df.head()

真偽値の文字列は TrueFalse がデフォルトですが,true_values=['Yes'], false_values=['No'] のようなオプションで指定できます。

データの書き出し

データフレームのCSV形式での書き出しは pandas.DataFrame.to_csv() を使います。デフォルトはUTF-8ですが,Excel互換のBOM付きUTF-8(行末CRLF)でインデックスを付けずに保存するには次のようにします:

df.to_csv("filename.csv", index=False, encoding="utf_8_sig", line_terminator="\r\n")

データが10進で切りの良い値でも,微妙な誤差が入ることがあります。例えばオプション float_format="%.14g" を付けて,わざと精度を落として書き込むといった対策がありそうです。

Excel形式での書き出しは pandas.DataFrame.to_excel() です。エンジンは openpyxl または xlsxwriter が指定できます。前者のほうが多機能のようです。あらかじめ conda install openpyxl または pip install openpyxl などと打ち込んでインストールしておきます。

df.to_excel("filename.xlsx")

JSON形式での書き出しは to_json() です:

df = pd.DataFrame({"A":[1,2],"B":[1.5,2.5]})
df.to_json()  #==> '{"A":{"0":1,"1":2},"B":{"0":1.5,"1":2.5}}'
df.to_json(orient="records")
#==> '[{"A":1,"B":1.5},{"A":2,"B":2.5}]'

なかなか簡潔なJSONになりません。次のような奥の手もあるようです:

import json

json.dumps(df.to_dict(orient="list"))
#==> '{"A": [1, 2], "B": [1.5, 2.5]}'
json.dumps(df.to_dict(orient="list"), separators=(',',':'))
#==> '{"A":[1,2],"B":[1.5,2.5]}'

全部メモリに入れないで処理する

全体を DataFrame に読み込まず行単位で操作するには,標準ライブラリ csvcsv.reader()csv.writer() を使います。例:

import csv

with open('file.csv') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

Last modified: