2019/01/25にリリースされたpandas v0.24.0ですが、
非常によいアップデートなので、いまさらながら紹介しようかなと思います。
注目ポイント
- Integer型のNaN対応!
- Extension Types中心の設計!
- ~(チルダ)問題解決
- タイムゾーン問題解決
0.24というバージョンについて
はじめに言っておきたいのが、この0.24というバージョン。
神アプデです。
今までのpandasの「ウーン。。。🤔」ポイントが解決されていますので、まだ使ってないという方は下にあげるような機能を中心に、是非触ってみて欲しいです。
注目ポイントピックアップ
1. ついにInt64のNaNに対応
早速みてみましょう。
まずは、従来の動き。
>>> # Int型のA列と、Float型のB列を持つデータフレーム。
>>> df = pandas.DataFrame({
'A': [1,2,3],
'B': [1.1, 1.2, 1.3]
})
>>> df
A B
0 1 1.1
1 2 1.2
2 3 1.3
>>> # A, Bともに、3行目をNaNにしていく。
>>> df.loc[2, 'A'] = None
>>> df.loc[2, 'B'] = None
>>> df
A B
0 1.0 1.1
1 2.0 1.2
2 NaN NaN
いやいやいや。1.0ってなんですか?
というのが従来のpandas。
続いて、0.24の動き。
>>> # Int64, Float64を明示してデータフレーム生成。
>>> # Int64は、pandas Extension Type。
>>> df2 = pandas.DataFrame({
'A': pandas.Series([1, 2, 3], dtype='Int64'),
'B': pandas.Series([1.1, 1.2, 1.3], dtype='Float64')
})
>>> df2
A B
0 1 1.1
1 2 1.2
2 3 1.3
>>> # A, Bともに、3行目をNaNにしていく。
>>> df2.loc[2, 'A'] = None
>>> df2.loc[2, 'B'] = None
>>> df2
A B
0 1 1.1
1 2 1.2
2 NaN NaN
>>> df2.A
0 1
1 2
2 NaN
Name: A, dtype: Int64
おー!確かに、Int64が維持されています!
さすが0.24!
ちなみに、メリットを感じるのは、こんなケース。
| value | |
|---|---|
| 0 | 0.0 |
| 1 | 1.0 |
| 2 | 2.0 |
| 3 | 3.0 |
| 4 | NaN |
| 5 | 5.0 |
| 6 | 6.0 |
| 7 | 6.999999999999999 |
| 8 | 8.0 |
| 9 | 9.000000000000002 |
| 10 | 10.0 |
こういうの、意外とあるんですよね。
2. Extension Types
このExtension Typesなる存在、実は0.23から入ってます。
公式の解説ページにあるとおり、numpy.dtypeのようなカスタム型をpandasに持ち込む仕組みです。
0.24では、ExtensionArrayなる機能が追加され、カスタム型に対応したnumpy.arrayのような立ち位置で使用可能なようです。
さて、このExtension Typesですが、「実際役に立つんか?」と疑問を感じる方が多いと思います。
そんな方のために、Extension Typesが役に立っているシーン・役に立つであろうシーンの例を挙げておきます。
- 最近対応した
Categorical型は実はExtensionDtype - 上記の
NaN対応Int64型は実はExtensionDtype -
0.24.0にて追加のInterval型もExtensionDtype - 緯度経度を扱う
Geo型など、役立ちそう - 多角形などの図形を扱う
Polygon型など、役立ちそう - カテゴリカルデータの組み合わせを表現する
CategoricalCombination型など、役立ちそう
3. ~(チルダ)対応
DataFrame.to_json(), DataFrame.to_csv(), DataFrame.to_pickle(), and other export methods now support tilde(~) in path argument. (GH23473)
っていうね。なんで今まで~使えなかったんだよっ!
なんども忘れてFileNotFoundに苦しめられましたが、これにて解放です。
4. タイムゾーン対応
今まで、to_datetimeやDatetimeIndexなど一部機能にて、タイムゾーンをガン無視するスタイルのpandasでしたが、ちゃんとtzがキープされるようになりました。これは、うれしい人が多いのでは?
公式からのコピペですが、以前は
In [2]: pd.to_datetime("2015-11-18 15:30:00+05:30")
Out[2]: Timestamp('2015-11-18 10:00:00')
In [3]: pd.Timestamp("2015-11-18 15:30:00+05:30")
Out[3]: Timestamp('2015-11-18 15:30:00+0530', tz='pytz.FixedOffset(330)')
# Different UTC offsets would automatically convert the datetimes to UTC (without a UTC timezone)
In [4]: pd.to_datetime(["2015-11-18 15:30:00+05:30", "2015-11-18 16:30:00+06:30"])
Out[4]: DatetimeIndex(['2015-11-18 10:00:00', '2015-11-18 10:00:00'], dtype='datetime64[ns]', freq=None)
のように、勝手にutcに変えちゃうマンでしたが、今後は、
In [56]: pd.to_datetime("2015-11-18 15:30:00+05:30")
Out[56]: Timestamp('2015-11-18 15:30:00+0530', tz='pytz.FixedOffset(330)')
In [57]: pd.Timestamp("2015-11-18 15:30:00+05:30")
Out[57]: Timestamp('2015-11-18 15:30:00+0530', tz='pytz.FixedOffset(330)')
In [63]: pd.to_datetime(["2015-11-18 15:30:00+05:30",
....: "2015-11-18 16:30:00+06:30"], utc=True)
....:
Out[63]: DatetimeIndex(['2015-11-18 10:00:00+00:00', '2015-11-18 10:00:00+00:00'], dtype='datetime64[ns, UTC]', freq=None)
タイムゾーンを維持したり、指定のタイムゾーンに変換したりできるようになります。
また、タイムゾーンが混合しているCSVファイルの読み込み時にも、個別にタイムゾーンを維持してくれるようです。
In [64]: import io
In [65]: content = """\
....: a
....: 2000-01-01T00:00:00+05:00
....: 2000-01-01T00:00:00+06:00"""
....:
In [66]: df = pd.read_csv(io.StringIO(content), parse_dates=['a'])
In [67]: df.a
Out[67]:
0 2000-01-01 00:00:00+05:00
1 2000-01-01 00:00:00+06:00
Name: a, Length: 2, dtype: object
今後の期待感
1. カスタムJOINできるようにしてほしい
今後の期待機能を語る上で外せない最重要ポイント。それはJOIN処理(pandas.merge, pandas.DataFrame.join)です。
なぜか。例えば、こんなケース。
>>> class A():
def __init__(self, v):
self.v = v
def __eq__(self, u):
return self.v % u == 0
def __repr__(self):
return f'A(v={self.v})'
>>> df = pandas.DataFrame({'a': [A(5), A(10), A(15)]})
>>> df
a
0 A(v=5)
1 A(v=10)
2 A(v=15)
>>> df2 = pandas.DataFrame({'b': [A(3), A(5)], 'c': [3, 5]})
>>> df2
b c
0 A(v=3) 3
1 A(v=5) 5
クラスAは、割り切れる値に対して、同値と判定します。
感覚的には、JOIN処理を通して、
a b c
0 A(v=5) A(v=5) 5
1 A(v=10) A(v=5) 5
2 A(v=15) A(v=3) 3
3 A(v=15) A(v=5) 5
こんな結果が出てきて欲しい。そんなケース。
さっそくやってみよう!
>>> pandas.merge(df, df2, left_on=['a'], right_on=['b'])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-131-00ec1523c45d> in <module>()
----> 1 pandas.merge(df, df2, left_on=['a'], right_on=['b'])
...
~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/pandas/core/reshape/merge.py in _factorize_keys(lk, rk, sort)
1659 rizer = klass(max(len(lk), len(rk)))
1660
-> 1661 llab = rizer.factorize(lk)
1662 rlab = rizer.factorize(rk)
1663
pandas/_libs/hashtable.pyx in pandas._libs.hashtable.Factorizer.factorize()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_labels()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable._unique()
TypeError: unhashable type: 'A'
というような感じで、hash使いたいマンなわけです。同値関係をカスタムできません。
ExtensionDtypeを使っても、JOINの際にはうまく動いてくれないです。(勘違いならすみません。。。知ってる人がいたら教えてください。。。)
てな訳で、JOIN処理もカスタムできるようにして欲しい。
2. ExtensionDtypeのメモリ
In [2]: # 'Int64' ExtensionDtype使う場合
df = pandas.DataFrame({'a': [1,2,3,4,5],
'b': pandas.Series([1,2,3,4,5], dtype='Int64'),
'c': ['aa', 'bb', 'cc', 'dd', 'ee']})
asizeof.asizeof(df)
Out[2]: 10800
In [3]: # 使わない場合
df = pandas.DataFrame({'a': [1,2,3,4,5],
'b': pandas.Series([1,2,3,4,5]),
'c': ['aa', 'bb', 'cc', 'dd', 'ee']})
asizeof.asizeof(df)
Out[3]: 9768
In [4]: # そのそも列がない場合
df = pandas.DataFrame({'a': [1,2,3,4,5],
'c': ['aa', 'bb', 'cc', 'dd', 'ee']})
asizeof.asizeof(df)
Out[4]: 8192
In [5]: # str型のc列がない場合
df = pandas.DataFrame({'a': [1,2,3,4,5]})
asizeof.asizeof(df)
Out[5]: 5712
In [6]: # 空のデータフレーム
df = pandas.DataFrame()
asizeof.asizeof(df)
Out[6]: 2920
という感じで、サイズ変化をまとめると、こんな感じ。
| 変数 | サイズ変化 |
|---|---|
| 空のデータフレーム | 2920 |
| str型のc列(5要素) | 2792 |
| builtin-int型のb列 | 1576 |
| Int64型のb列 | 2608 |
約1.5倍ほどメモリを使用しています。
理由は、ExtensionDtypeは、内部的にはObject型とみなされているから。
仕方ないことなんですけどね、本音を言えばなんとかして欲しい。
まとめ
重要な新機能
- NaNを保持できるInt64型
-
ExtensionDtypeとExtensionArrayでカスタム型対応
重要な修正機能
- パスに~をつけてもOKになった
- タイムゾーンを正しく扱うようになった
今後の期待
- ハッシュベースでないJOIN
- ExtensionDtype型やObject型の省メモリ化
という感じで、pandas0.24.0のリリースと今後の期待について書きました。
「お、アップデートしとこ!」と思ってくれた方がいたら、幸いです。
今後も、データ分析ライブラリのリリースをまとめたり、小技をまとめたりする予定です。
これ、誰が読むんやろ。