この記事は何?公式ドキュメントの(個人的な)翻訳だよ
Essential basic functionality — pandas 1.4.1 documentation
(2022年3月5日追記:pandas URLが変更されていてリンク切れになっていたので最新のURLに変えましたが、下記は1.0.3時点の公式ドキュメントの翻訳です。)
pandasの型であるdtypeについての公式ドキュメント(v 1.0.3時点)を翻訳した。
内容は、pandasのSeriesやDataframeに関して
- dtypeの基本事項
- 型を判定・確認する方法、
- 他の型と結合した時のアップキャスト
- astype()関数を使って指定した型に変更・変換する方法
- infer_objects()、to_numeric()関数を使って指定した型に変更・変換する方法
- 落とし穴(nanが入ると勝手にdtypeが変わってしまう仕様について)
などである。
(元は公式ドキュメントだが、この翻訳は誰かが公認したわけではなく、個人的・非公式のものである。念のため。)
参考になる書籍が少ないよ
pandasを使ってデータ処理をしたいときに、データの型を操作することが主目的だという場合は少ないだろう。
しかし、型システムは根幹の部分で必ず使われているため、型をどう扱っているかの理解が必要になる場合もある。
pandasを使う上で避けて通れない話だが、文献を見ても意外と型の記述は少ない。
pandasを作ったWes McKinneyが書いた「Pythonによるデータ分析入門 第2版 ―NumPy、pandasを使ったデータ処理」にも、dtypeの話はあまり載っていなかった。
インプレス社の「Pythonデータ分析/機械学習のための基本コーディング! pandasライブラリ活用入門」の第7章「データ型の概要と変換」では、まるまるdtypeの話をしている。
pp150〜159の10ページあり、私が見た中で最も分量の多い記述である。
書籍を見てもまとまった記述は少ないし、pandas公式ドキュメントを参考にするのが一番だろう。
ということで、公式ドキュメントの該当部分を翻訳した。
dtypes
pandasはほとんどの部分において、Seriesと、DataFrameの個々の列に対して、NumPyのarrayとdtypeを使用している。
NumPyはfloat, int, bool, timedelta64[ns] and datetime64[ns]をサポートしている。
(NumPyは、タイムゾーンの区別のあるdatetimeをサポートしないことに注意。)
pandasやサードパーティのライブラリは、いくつかの点においてNumPyの型システムを拡張している。
このセクションでは、pandasの内部で実施されている拡張について説明する。
pandas上で動作する、自作の拡張を書く方法については、[拡張データ型]を参照のこと。
データ型の拡張をしたサードパーティ製ライブラリの一覧については、[拡張データ型]を参照のこと。
以下の表ではpandasの拡張型を全て列挙している。それぞれの型についての詳細な説明は、各ドキュメントを参照のこと。
Kind of Data Data Type Scalar Array Documentation
tz-aware datetime DatetimeTZDtype Timestamp arrays.DatetimeArray Time zone handling
Categorical CategoricalDtype (none) Categorical Categorical data
period (time spans) PeriodDtype Period arrays.PeriodArray Time span representation
sparse SparseDtype (none) arrays.SparseArray Sparse data structures
intervals IntervalDtype Interval arrays.IntervalArray IntervalIndex
nullable integer Int64Dtype, … (none) arrays.IntegerArray Nullable integer data type
pandasは文字列を格納するのに、objectというdtypeを用いる。
最後に、任意のオブジェクトは、object dtypeを使えば格納することができる。
しかしこのやり方は可能な限り避けるべきである。
(パフォーマンスが悪くなるため、そして、他のライブラリやメソッドとの相互運用性のためである。オブジェクトの変換 の節を参照。)
DataFrameにはdtypesという便利な属性があり、dtypesは各行のデータ型が格納されたSeriesを返す。
In [328]: dft = pd.DataFrame({'A': np.random.rand(3),
.....: 'B': 1,
.....: 'C': 'foo',
.....: 'D': pd.Timestamp('20010102'),
.....: 'E': pd.Series([1.0] * 3).astype('float32'),
.....: 'F': False,
.....: 'G': pd.Series([1] * 3, dtype='int8')})
.....:
In [329]: dft
Out[329]:
A B C D E F G
0 0.035962 1 foo 2001-01-02 1.0 False 1
1 0.701379 1 foo 2001-01-02 1.0 False 1
2 0.281885 1 foo 2001-01-02 1.0 False 1
In [330]: dft.dtypes
Out[330]:
A float64
B int64
C object
D datetime64[ns]
E float32
F bool
G int8
dtype: object
Seriesオブジェクトについては、dtype属性を用いる。
In [331]: dft['A'].dtype
Out[331]: dtype('float64')
pandasオブジェクトの一つの列の中に、複数のdtypeからなるデータがある場合
その列のdtypeは、全てのデータの型が格納できるような型が選ばれる(object型が最も汎用的である)。
In [332]: pd.Series([1, 2, 3, 4, 5, 6.])
Out[332]:
0 1.0
1 2.0
2 3.0
3 4.0
4 5.0
5 6.0
dtype: float64
In [333]: pd.Series([1, 2, 3, 6., 'foo'])
Out[333]:
0 1
1 2
2 3
3 6
4 foo
dtype: object
DataFrame.dtypes.value_counts()を呼び出すと、DataFrameの中にそれぞれのデータ型の列がいくつあるかが分かる。
In [334]: dft.dtypes.value_counts()
Out[334]:
bool 1
object 1
int8 1
float64 1
float32 1
int64 1
datetime64[ns] 1
dtype: int64
数値型のdtypeは、伝播し、DataFrameの中で共存できる。
(dtypeキーワード、渡されたndarray、渡されたSeriesのいずれかを通じて)dtypeが渡されたならば、
DataFrameの操作の中でdtypeは保存される。
さらに、異なる数値型は結合されない(訳注:ある列と別の列が独立なので、別の型で共存できる、ということを言っているのだろう)
以下の例を見れば、動作の一端が分かるだろう。
In [335]: df1 = pd.DataFrame(np.random.randn(8, 1), columns=['A'], dtype='float32')
In [336]: df1
Out[336]:
A
0 0.224364
1 1.890546
2 0.182879
3 0.787847
4 -0.188449
5 0.667715
6 -0.011736
7 -0.399073
In [337]: df1.dtypes
Out[337]:
A float32
dtype: object
In [338]: df2 = pd.DataFrame({'A': pd.Series(np.random.randn(8), dtype='float16'),
.....: 'B': pd.Series(np.random.randn(8)),
.....: 'C': pd.Series(np.array(np.random.randn(8),
.....: dtype='uint8'))})
.....:
In [339]: df2
Out[339]:
A B C
0 0.823242 0.256090 0
1 1.607422 1.426469 0
2 -0.333740 -0.416203 255
3 -0.063477 1.139976 0
4 -1.014648 -1.193477 0
5 0.678711 0.096706 0
6 -0.040863 -1.956850 1
7 -0.357422 -0.714337 0
In [340]: df2.dtypes
Out[340]:
A float16
B float64
C uint8
dtype: object
デフォルトの動作
デフォルトでは、整数の型はint64、浮動小数点数の型はfloat64であり、これはプラットフォーム(32ビットか64ビットか)に関係ない。以下のコードの結果は、全てint64 dtypeである。
In [341]: pd.DataFrame([1, 2], columns=['a']).dtypes
Out[341]:
a int64
dtype: object
In [342]: pd.DataFrame({'a': [1, 2]}).dtypes
Out[342]:
a int64
dtype: object
In [343]: pd.DataFrame({'a': 1}, index=list(range(2))).dtypes
Out[343]:
a int64
dtype: object
ただし、NumPyが配列を作るときには、型の選択はプラットフォームに依存するので注意。
以下のコードの結果は、32ビットのプラットフォームで実行した場合にはint32となる。
In [344]: frame = pd.DataFrame(np.array([1, 2]))
アップキャスト
型は、他の型と結合した場合にアップキャストされる可能性がある。すなわち、現在の型よりも上位の型に(例えばintからfloatに)変換される。
In [345]: df3 = df1.reindex_like(df2).fillna(value=0.0) + df2
In [346]: df3
Out[346]:
A B C
0 1.047606 0.256090 0.0
1 3.497968 1.426469 0.0
2 -0.150862 -0.416203 255.0
3 0.724370 1.139976 0.0
4 -1.203098 -1.193477 0.0
5 1.346426 0.096706 0.0
6 -0.052599 -1.956850 1.0
7 -0.756495 -0.714337 0.0
In [347]: df3.dtypes
Out[347]:
A float32
B float64
C float64
dtype: object
DataFrame.to_numpy() は、単一のdtypeのNumPy配列を返し、
その型はいわば「最小公倍数」、すなわち、元のDataFrameの全ての型を格納できるdtypeとなる。
これによりアップキャストが発生する場合がある。
In [348]: df3.to_numpy().dtype
Out[348]: dtype('float64')
astype
明示的に1つのdtypeから他のdtypeに変換するには、astypeメソッドを使うことができる。
astypeメソッドは*1デフォルトではコピーを返す。
これはdtypeが変わらない場合でも同じである。(動作を変更するには、copy=False
パラメータを渡す。)
さらに、astype操作が無効である場合、astypeメソッドは例外を発生させる。
アップキャストは常にNumPyの規則に従う。
ある演算に2つの異なるdtypeが関与している場合は、演算の結果には より汎用的なほうの型が使われる。
In [349]: df3
Out[349]:
A B C
0 1.047606 0.256090 0.0
1 3.497968 1.426469 0.0
2 -0.150862 -0.416203 255.0
3 0.724370 1.139976 0.0
4 -1.203098 -1.193477 0.0
5 1.346426 0.096706 0.0
6 -0.052599 -1.956850 1.0
7 -0.756495 -0.714337 0.0
In [350]: df3.dtypes
Out[350]:
A float32
B float64
C float64
dtype: object
In [351]: df3.astype('float32').dtypes
Out[351]:
A float32
B float32
C float32
dtype: object
astypeを使って、一部の列を特定の型に変更する。
In [352]: dft = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6], 'c': [7, 8, 9]})
In [353]: dft[['a', 'b']] = dft[['a', 'b']].astype(np.uint8)
In [354]: dft
Out[354]:
a b c
0 1 4 7
1 2 5 8
2 3 6 9
In [355]: dft.dtypes
Out[355]:
a uint8
b uint8
c int64
dtype: object
New in version 0.19.0.
astype()にdictを渡すことで、ある列(複数でもよい)を特定の型に変換する。
In [356]: dft1 = pd.DataFrame({'a': [1, 0, 1], 'b': [4, 5, 6], 'c': [7, 8, 9]})
In [357]: dft1 = dft1.astype({'a': np.bool, 'c': np.float64})
In [358]: dft1
Out[358]:
a b c
0 True 4 7.0
1 False 5 8.0
2 True 6 9.0
In [359]: dft1.dtypes
Out[359]:
a bool
b int64
c float64
dtype: object
注意:
astype()およびloc()を用いて、列の部分集合を指定した型へと変換しようとした場合、アップキャストが発生する。
[]はdtypeを括弧内で指定された型に変換するが*2、
その一方でloc()はオブジェクトを現在のdtypeに戻して代入しようとする。
ゆえに、下のコードの結果は意図した通りにならない。
In [360]: dft = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6], 'c': [7, 8, 9]})
In [361]: dft.loc[:, ['a', 'b']].astype(np.uint8).dtypes
Out[361]:
a uint8
b uint8
dtype: object
In [362]: dft.loc[:, ['a', 'b']] = dft.loc[:, ['a', 'b']].astype(np.uint8)
In [363]: dft.dtypes
Out[363]:
a int64
b int64
c int64
dtype: object
オブジェクトの変換
pandasには、object dtypeから他の型に変換するための様々な関数が用意されている。
データが既に正しい型であるが、object型の配列に格納されている場合、
DataFrame.infer_objects() と Series.infer_objects()メソッドは正しい型にソフトに変換するのに使うことができる。(訳注:soft convertはこれ以上の説明が無い。「ユーザが型を指定せずに、データの中身から型を類推して変換してもらうこと」を指すか?)
In [364]: import datetime
In [365]: df = pd.DataFrame([[1, 2],
.....: ['a', 'b'],
.....: [datetime.datetime(2016, 3, 2),
.....: datetime.datetime(2016, 3, 2)]])
.....:
In [366]: df = df.T
In [367]: df
Out[367]:
0 1 2
0 1 a 2016-03-02
1 2 b 2016-03-02
In [368]: df.dtypes
Out[368]:
0 object
1 object
2 datetime64[ns]
dtype: object
データを転置したので、元々の型推測によって全ての列がobject型として格納されているが、infer_objectsを使うと修正される。
(訳注:「全ての列」と言いつつdatetime64[ns]型の列がある。謎。)
In [369]: df.infer_objects().dtypes
Out[369]:
0 int64
1 object
2 datetime64[ns]
dtype: object
1次元の配列もしくはスカラーに対して、特定の型にハード変換するためには、以下の関数が使用できる。
(訳注:hard convertはこれ以上の説明が無い。「ユーザが『数値型にせよ』『日付型にせよ』などと型を指定して、データを変換すること」を指すか?)
to_numeric() (数値のdtypeへの変換)
In [370]: m = ['1.1', 2, 3]
In [371]: pd.to_numeric(m)
Out[371]: array([1.1, 2. , 3. ])
to_datetime() (datatime objectへの変換)
In [372]: import datetime
In [373]: m = ['2016-07-09', datetime.datetime(2016, 3, 2)]
In [374]: pd.to_datetime(m)
Out[374]: DatetimeIndex(['2016-07-09', '2016-03-02'], dtype='datetime64[ns]', freq=None)
to_timedelta() (timedelta objectへの変換)
In [375]: m = ['5us', pd.Timedelta('1day')]
In [376]: pd.to_timedelta(m)
Out[376]: TimedeltaIndex(['0 days 00:00:00.000005', '1 days 00:00:00'], dtype='timedelta64[ns]', freq=None)
型変換を強制する際には、errors引数を渡すことができる。(訳注:errors引数については「Pythonデータ分析/機械学習のための基本コーディング! pandasライブラリ活用入門」の第7章「データ型の概要と変換」にも同様の記述あり。)
これにより、希望するdtypeやobjectに変換できなかった要素について、pandasがどう扱うかを指定する。
デフォルトではerrors='raise'
であり、これは変換の過程の中で何らかのエラーに遭遇したらエラーが上がるという意味である。
しかし、もしerrors='coerce'
を指定すれば、エラーは無視され、
pandasは問題を引き起こした要素をpd.NaT(datetime と timedeltaの場合)もしくはnp.nan(数値型の場合)に変換する。
この動作が便利かもしれないのは、読み込んだデータのほとんどが所望のdtype(例えば数値型やdatetime)であるが、
時々適合しない要素も混ざっていて、それらの要素を欠損として扱いたい場合である。
In [377]: import datetime
In [378]: m = ['apple', datetime.datetime(2016, 3, 2)]
In [379]: pd.to_datetime(m, errors='coerce')
Out[379]: DatetimeIndex(['NaT', '2016-03-02'], dtype='datetime64[ns]', freq=None)
In [380]: m = ['apple', 2, 3]
In [381]: pd.to_numeric(m, errors='coerce')
Out[381]: array([nan, 2., 3.])
In [382]: m = ['apple', pd.Timedelta('1day')]
In [383]: pd.to_timedelta(m, errors='coerce')
Out[383]: TimedeltaIndex([NaT, '1 days'], dtype='timedelta64[ns]', freq=None)
errors引数には3番目の選択肢errors='ignore'
があり、この場合、所望のデータ型に変換するときに何らかのエラーが発生したら、渡されたデータを変換せずにそのまま返す。
In [384]: import datetime
In [385]: m = ['apple', datetime.datetime(2016, 3, 2)]
In [386]: pd.to_datetime(m, errors='ignore')
Out[386]: Index(['apple', 2016-03-02 00:00:00], dtype='object')
In [387]: m = ['apple', 2, 3]
In [388]: pd.to_numeric(m, errors='ignore')
Out[388]: array(['apple', 2, 3], dtype=object)
In [389]: m = ['apple', pd.Timedelta('1day')]
In [390]: pd.to_timedelta(m, errors='ignore')
Out[390]: array(['apple', Timedelta('1 days 00:00:00')], dtype=object)
オブジェクトの変換に加えて、to_numeric()にはもう一つの引数downcastがある。
引数downcastを使うと、新しく数値型になった(もしくは既に数値型の)データを、より小さいdtypeにダウンキャストできる。
これによってメモリが節約できる。
In [391]: m = ['1', 2, 3]
In [392]: pd.to_numeric(m, downcast='integer')
Out[392]: array([1, 2, 3], dtype=int8)
In [393]: pd.to_numeric(m, downcast='signed')
Out[393]: array([1, 2, 3], dtype=int8)
In [394]: pd.to_numeric(m, downcast='unsigned')
Out[394]: array([1, 2, 3], dtype=uint8)
In [395]: pd.to_numeric(m, downcast='float')
Out[395]: array([1., 2., 3.], dtype=float32)
上述したメソッドは、1次元の配列、リスト、スカラーにしか使えない。
DataFrameのような多次元のオブジェクトに直接使うことはできない。
しかし、apply()を使えば、効率的に各列に関数を適用(apply)できる。
In [396]: import datetime
In [397]: df = pd.DataFrame([
.....: ['2016-07-09', datetime.datetime(2016, 3, 2)]] * 2, dtype='O')
.....:
In [398]: df
Out[398]:
0 1
0 2016-07-09 2016-03-02 00:00:00
1 2016-07-09 2016-03-02 00:00:00
In [399]: df.apply(pd.to_datetime)
Out[399]:
0 1
0 2016-07-09 2016-03-02
1 2016-07-09 2016-03-02
In [400]: df = pd.DataFrame([['1.1', 2, 3]] * 2, dtype='O')
In [401]: df
Out[401]:
0 1 2
0 1.1 2 3
1 1.1 2 3
In [402]: df.apply(pd.to_numeric)
Out[402]:
0 1 2
0 1.1 2 3
1 1.1 2 3
In [403]: df = pd.DataFrame([['5us', pd.Timedelta('1day')]] * 2, dtype='O')
In [404]: df
Out[404]:
0 1
0 5us 1 days 00:00:00
1 5us 1 days 00:00:00
In [405]: df.apply(pd.to_timedelta)
Out[405]:
0 1
0 00:00:00.000005 1 days
1 00:00:00.000005 1 days
落とし穴
(訳注:セクション名について*3)
整数型のデータに対して要素の選択を実行すると、データが浮動小数点数にアップキャストされがちである
*4。
結果にnanが含まれていない場合には、入力データのdtypeは保存される。
[整数のNAのサポート]も参照。
In [406]: dfi = df3.astype('int32')
In [407]: dfi['E'] = 1
In [408]: dfi
Out[408]:
A B C E
0 1 0 0 1
1 3 1 0 1
2 0 0 255 1
3 0 1 0 1
4 -1 -1 0 1
5 1 0 0 1
6 0 -1 1 1
7 0 0 0 1
In [409]: dfi.dtypes
Out[409]:
A int32
B int32
C int32
E int64
dtype: object
In [410]: casted = dfi[dfi > 0]
In [411]: casted
Out[411]:
A B C E
0 1.0 NaN NaN 1
1 3.0 1.0 NaN 1
2 NaN NaN 255.0 1
3 NaN 1.0 NaN 1
4 NaN NaN NaN 1
5 1.0 NaN NaN 1
6 NaN NaN 1.0 1
7 NaN NaN NaN 1
In [412]: casted.dtypes
Out[412]:
A float64
B float64
C float64
E int64
dtype: object
一方で、浮動小数点数のdtypeはnanが含まれても変更されない。
In [413]: dfa = df3.copy()
In [414]: dfa['A'] = dfa['A'].astype('float32')
In [415]: dfa.dtypes
Out[415]:
A float32
B float64
C float64
dtype: object
In [416]: casted = dfa[df2 > 0]
In [417]: casted
Out[417]:
A B C
0 1.047606 0.256090 NaN
1 3.497968 1.426469 NaN
2 NaN NaN 255.0
3 NaN 1.139976 NaN
4 NaN NaN NaN
5 1.346426 0.096706 NaN
6 NaN NaN 1.0
7 NaN NaN NaN
In [418]: casted.dtypes
Out[418]:
A float32
B float64
C float64
dtype: object