この記事は、PythonのPandasモジュールを使用して、CSVファイルの読み書きをする実践テクニックをまとめたものになっています。
PythonでCSVファイルを処理する方法はPandas以外にもたくさんありますが、私はPandas一択派です。
- Pandasはデータの加工・分析に特化したモジュール
- 何千何万行に渡るデータ量でも高速に処理できる
- 複数のCSVをまとめて処理できる
- NumPyやSciPy、Matplotlibモジュールと親和性がある
目次
CSVを読み込む
pandas
モジュールのread_csv
メソッドを使用すると、CSVファイルのデータをpandas.DataFrame
として変数に格納することができます。
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html
import pandas as pd
df = pd.read_csv('{{ 読み込むCSVファイルのパス }}')
pandas.read_csv
でCSVファイルを読み込む実践テクニック集は、次の通りです。
- 日本語が含まれる場合
- 郵便番号や電話番号が含まれる場合
- ヘッダーが無い場合
- インデックスを設定したい場合
- 任意の行数スキップして読み込みたい場合
- 指定した列だけ読み込みたい場合
- 複数のCSVを1つのDataFrameに読み込みたい場合
それぞれ見てみましょう。
日本語が含まれる場合
日本語が含まれるCSVファイル読み込みたいときに
"col1","col2","col3"
"a","あいうえお","1"
"b","かきくけこ","2"
"c","さしすせそ","3"
普通にread_csv
メソッドを使うと、UnicodeDecodeError
が発生します。
df = pd.read_csv('sample.csv')
print(df)
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte
そこで、encoding
パラメータに日本語が扱える文字コード(shift-jis
やcp932
など)を指定することで、エラーを回避することができます。
実際の文字コードには、shift-jis
の拡張であるcp932
を指定すれば間違いないでしょう。
df = pd.read_csv('sample.csv', encoding='cp932')
print(df)
# col1 col2 col3
# 0 a あいうえお 1
# 1 b かきくけこ 2
# 2 c さしすせそ 3
郵便番号や電話番号が含まれる場合
郵便番号や電話番号が含まれるCSVファイル読み込みたいときに
"col1","col2","col3"
"1001","8100001","08011112222"
"1002","1008602","07012345678"
"1003","0010010","09087654321"
普通にread_csv
メソッドを使うと、住所や番号によっては先頭の0が抜けます。
df = pd.read_csv('sample.csv')
print(df)
# col1 col2 col3
# 0 1001 8100001 8011112222
# 1 1002 1008602 7012345678
# 2 1003 10010 9087654321
0落ちを回避するためには、CSVファイルを読み込むときに各カラムのデータの型を文字列(object
)に指定します。
- 全ての列を文字列にする場合:
dtype=object
- 指定した列だけ文字列にする場合:
dtype={'col2': object, 'col3': object}
おまけでdtype
パラメータに設定できる型を下のテーブルにまとめました
Pandas dtype | Python type |
---|---|
object |
str |
int64 |
int |
float64 |
float |
bool |
bool |
datetime64 |
NA |
timedelta[ns] |
NA |
category |
NA |
全ての列を文字列にして取り込む
df = pd.read_csv('sample.csv', dtype=object)
print(df)
# col1 col2 col3
# 0 1001 8100001 08011112222
# 1 1002 1008602 07012345678
# 2 1003 0010010 09087654321
print(df.dtypes)
# col1 object
# col2 object
# col3 object
# dtype: object
指定した列だけ文字列にして取り込む
df = pd.read_csv('sample.csv', dtype={'col2': object, 'col3': object})
print(df)
# col1 col2 col3
# 0 1001 8100001 08011112222
# 1 1002 1008602 07012345678
# 2 1003 0010010 09087654321
print(df.dtypes)
# col1 int64
# col2 object
# col3 object
# dtype: object
ヘッダーが無い場合
CSVファイルには、次のようにヘッダーが存在せず、データしか入っていないものもあります。
"1001","a","A"
"1002","b","B"
"1003","c","C"
そのままread_csv
メソッドで読み込むと一行目がヘッダーとして読み込まれます。
df = pd.read_csv('sample.csv')
print(df)
# 1001 a A
# 0 1002 b B
# 1 1003 c C
一行目をヘッダーとして読み込ませたくない場合は、names
パラメータに列名のリストを指定します。
df = pd.read_csv('sample.csv', names=['col1', 'col2', 'col3'])
print(df)
# col1 col2 col3
# 0 1001 a A
# 1 1002 b B
# 2 1003 c C
インデックスを設定したい場合
"col1","col2","col3"
"1001","a","A"
"1002","b","B"
"1003","c","C"
普通にread_csv
メソッドで読み込むと、インデックスは0からの連番になります。
df = pd.read_csv('sample.csv')
print(df)
# col1 col2 col3
# 0 1001 a A
# 1 1002 b B
# 2 1003 c C
読み込むときに指定した列をDataFrameのインデックスに設定したい場合、index_col
パラメータに列番号または列名のリストを指定します。
列番号のリストでインデックスを設定する
df = pd.read_csv('sample.csv', index_col=[0])
print(df)
# col2 col3
# col1
# 1001 a A
# 1002 b B
# 1003 c C
列名のリストでインデックスを設定する
df = pd.read_csv('sample.csv', index_col=['col1', 'col2'])
print(df)
# col3
# col1 col2
# 1001 a A
# 1002 b B
# 1003 c C
任意の行数スキップして読み込みたい場合
- 先頭から任意の行だけスキップして読み込みたい:
skiprows
- 指定した行番号をスキップして読み込みたい:
skiprows
- 末尾から任意の行だけスキップして読み込みたい:
skipfooter
- 先頭から数行だけ読み込みたい:
nrows
"col1","col2"
"1001","a"
"1002","b"
"1003","c"
"1004","d"
"1005","e"
"1006","f"
"1007","g"
"1008","h"
"1009","i"
"1010","j"
先頭から任意の行だけスキップして読み込みたい場合
先頭から任意の行だけスキップして読み込みたい場合は、skiprows
パラメータに整数値を指定します。
df = pd.read_csv('sample.csv', skiprows=3)
print(df)
# 1003 c
# 0 1004 d
# 1 1005 e
# 2 1006 f
# 3 1007 g
# 4 1008 h
# 5 1009 i
# 6 1010 j
指定した行番号をスキップして読み込みたい場合
指定した行番号をスキップして読み込みたい場合は、skiprows
パラメータに配列を指定します。
df = pd.read_csv('sample.csv', skiprows=[0, 2, 3, 9])
print(df)
# 1001 a
# 0 1004 d
# 1 1005 e
# 2 1006 f
# 3 1007 g
# 4 1008 h
# 5 1010 j
末尾から任意の行だけスキップして読み込みたい場合
末尾から任意の行だけスキップして読み込みたい場合は、skipfooter
パラメータに整数値を指定します。
環境によって、以下のような警告が表示されるので、engine='python'
も一緒に指定しましょう。
ParserWarning: Falling back to the 'python' engine because the 'c' engine does not support skipfooter; you can avoid this warning by specifying engine='python'.
df = pd.read_csv('sample.csv', skipfooter=5, engine='python')
print(df)
# col1 col2
# 0 1001 a
# 1 1002 b
# 2 1003 c
# 3 1004 d
# 4 1005 e
先頭から数行だけ読み込みたい場合
先頭から数行だけ読み込みたい場合は、nrows
パラメータに整数値(先頭行はカウントしない)を指定します。
サイズが大きいファイルの中身をちら見するときに使うことが多いです。
ParserWarning: Falling back to the 'python' engine because the 'c' engine does not support skipfooter; you can avoid this warning by specifying engine='python'.
df = pd.read_csv('sample.csv', nrows=5)
print(df)
# col1 col2
# 0 1001 a
# 1 1002 b
# 2 1003 c
# 3 1004 d
# 4 1005 e
指定した列だけ読み込みたい場合
読み込みたい列が決まっている場合、usecols
パラメータを使用します。
usecols
パラメータには読み込む列番号または列名をリストで指定します。1列だけ読み込む場合もリストを使います。
"col1","col2","col3","col4"
"1001","a","A","aA"
"1002","b","B","bB"
"1003","c","C","cC"
列番号のリストで読み込む列を指定する
df = pd.read_csv('sample.csv', usecols=[0, 1])
print(df)
# col1 col2
# 0 1001 a
# 1 1002 b
# 2 1003 c
列名のリストで読み込む列を指定する
df = pd.read_csv('sample.csv', usecols=["col2", "col3"])
print(df)
# col2 col3
# 0 a A
# 1 b B
# 2 c C
複数のCSVを1つのDataFrameに読み込みたい場合
複数のCSVファイルを1つのDataFrameにガッチャンコしたいときは
"col1","col2"
"1001","あいうえお"
"1002","かきくけこ"
"1003","さしすせそ"
"col1","col2"
"1004","たちつてと"
"1005","なにぬねの"
"1006","はひふへほ"
"col1","col2"
"1007","まみむめも"
"1008","やゆよ"
"1009","らりるれろ"
for
ループと標準モジュールのglop
を使用し、指定したディレクトリからCSVファイルをまとめて順番にインポートとして、pandas.concat
メソッドで、1つのデータフレームにマージします。
from glob import glob
csv_files_dir = glob('sample*.csv') # 『*』がワールドカード
marge_csv = []
for f in csv_files_dir:
marge_csv.append(pd.read_csv(f, encoding='cp932'))
# 1つのデータフレームに結合する
# ignore_index=Trueでインデックスをリセット
df = pd.concat(marge_csv, ignore_index=True)
print(df)
# col1 col2
# 0 1004 たちつてと
# 1 1005 なにぬねの
# 2 1006 はひふへほ
# 3 1007 まみむめも
# 4 1008 やゆよ
# 5 1009 らりるれろ
# 6 1001 あいうえお
# 7 1002 かきくけこ
# 8 1003 さしすせそ
ちゃんと結合されていますが、読み込まれるCSVファイルの順番がめちゃくちゃですね。
それもそのはず。変数csv_files_dir
に格納する前のglob('sample*.csv')
部分を確認します。
print(glob('sample*.csv'))
# ['sample2.csv', 'sample3.csv', 'sample1.csv']
ファイル名で昇順ソートして読み込ませたい場合、組み込み関数のsorted
メソッドを使用します。
print(sorted(glob('sample*.csv')))
# ['sample1.csv', 'sample2.csv', 'sample3.csv']
ファイル名で降順ソートして読み込ませたい場合、sorted
メソッドのreverse
パラメータの値をTrue
にします。
print(sorted(glob('sample*.csv'), reverse=True))
# ['sample3.csv', 'sample2.csv', 'sample1.csv']
余談ですが、リスト内包表記を使えば、可読性は置いておいて読み込む部分がワンライナーで書けるので、知っていると便利です。
リスト内包表記を使ったほうが処理が早くなるみたいなので、積極的に使ってみてはどうでしょうか。
marge_csv = []
for f in csv_files_dir:
marge_csv.append(pd.read_csv(f, encoding='cp932'))
marge_csv = [pd.read_csv(f, encoding='cp932') for f in csv_files_dir]
CSVを書き出す
pandas.DataFrame
のto_csv
メソッドを使用すると、DataFrameをCSVファイルとして書き出すことができます。
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_csv.html
df.to_csv('{{ 保存先のCSVファイルのパス }}')
pandas.DataFrame.to_csv
でCSVファイルを作成する実践テクニック集は、次の通りです。
- DataFrameのインデックスを無視して書き出したい場合
- 日本語が含まれる場合
- 任意の行数ごとに分割して書き出す
- 書き出すCSVのデータをダブルクオートしたい場合
それぞれ見てみましょう。
DataFrameのインデックスを無視して書き出したい場合
print(df)
# col1 col2 col3
# 0 1001 a A
# 1 1002 b B
# 2 1003 c C
DataFrameのインデックスを無視して書き出したい場合は、index
パラメータの値をFalse
に設定します。
import csv
df.to_csv('sample.csv', index=False)
col1,col2,col3
1001,a,A
1002,b,B
1003,c,C
日本語が含まれる場合
日本語が含まれた次のようなDataFrameをto_csv
で書き出すと、文字コードのデフォルトがshift-jis
のExcelでは、文字化けしてしまいます。
print(df)
# col1 col2 col3
# 0 1001 a あ
# 1 1002 b い
# 2 1003 c う
そこで、encoding
パラメータに日本語が扱える文字コード(shift-jis
やcp932
など)を指定することで、エラーを回避することができます。
df.to_csv('sample.csv', encoding='cp932')
,col1,col2,col3f
0,1001,a,あ
1,1002,b,い
2,1003,c,う
また、開発環境によって次のようなエラーが発生することがあります。
UnicodeEncodeError: 'cp932' codec can't encode character '\xa0'
その場合は、次のようにして無理やり回避させます。
with open('sample.csv', mode='w', encoding='cp932', errors='ignore') as f:
df.to_csv(f)
任意の行数ごとに分割して書き出す
import seaborn as sns # => pip install seaborn
iris = sns.load_dataset('iris')
print(iris)
# sepal_length sepal_width petal_length petal_width species
# 0 5.1 3.5 1.4 0.2 setosa
# 1 4.9 3.0 1.4 0.2 setosa
# 2 4.7 3.2 1.3 0.2 setosa
# 3 4.6 3.1 1.5 0.2 setosa
# 4 5.0 3.6 1.4 0.2 setosa
# ︙
# 145 6.7 3.0 5.2 2.3 virginica
# 146 6.3 2.5 5.0 1.9 virginica
# 147 6.5 3.0 5.2 2.0 virginica
# 148 6.2 3.4 5.4 2.3 virginica
# 149 5.9 3.0 5.1 1.8 virginica
# CSVファイルを20行ごとに分割し、それぞれCSVファイルとして書き出す
for index, df in iris.groupby(by=iris.index//20):
df.to_csv('sample_{}.csv'.format(index), index=False)
# sample_0.csv
# ︙
# sample_7.csv
書き出すCSVのデータをダブルクオートしたい場合
CSVファイルには、値がダブルクォーテーションで囲まれているものと囲まれていないものが存在します。
col1,col2,col3
1001,a,A
1002,b,B
1003,c,C
"col1","col2","col3"
"1001","a","A"
"1002","b","B"
"1003","c","C"
システムによってはダブルクオートされたCSVファイルじゃないと、取り込んだ際にエラーが発生することもあります。
quoting
パラメータを使用すると、どのようにダブルクオートさせるか指定して出力させることができます。
print(df)
# col1 col2 col3
# 0 1001 a A
# 1 1002 b B
# 2 1003 c C
import csv
df.to_csv('sample.csv', quoting=csv.QUOTE_ALL)
"","col1","col2","col3"
"0","1001","a","A"
"1","1002","b","B"
"2","1003","c","C"
quoting
パラメータには、csv.QUOTE_ALL
の値以外にも設定することができます。
デフォルト、csv.QUOTE_MINIMAL
quoting=csv.QUOTE_NONE
:すべてをタブルクオートさせずに出力したいquoting=csv.QUOTE_ALL
:すべてをタブルクオートさせて出力したいquoting=csv.QUOTE_NONNUMERIC
:数値だけクオートせずに、それ以外をダブルクオートさせて出力したいquoting=csv.QUOTE_MINIMAL
:デリミタや引用符、改行コードのような特別な文字を含む値だけをクオートさせて出力したい