3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

いまさらpandas0.24について

Posted at

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_datetimeDatetimeIndexなど一部機能にて、タイムゾーンをガン無視するスタイルの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型
  • ExtensionDtypeExtensionArrayでカスタム型対応

重要な修正機能

  • パスに~をつけてもOKになった
  • タイムゾーンを正しく扱うようになった
今後の期待
  • ハッシュベースでないJOIN
  • ExtensionDtype型やObject型の省メモリ化

という感じで、pandas0.24.0のリリースと今後の期待について書きました。
「お、アップデートしとこ!」と思ってくれた方がいたら、幸いです。

今後も、データ分析ライブラリのリリースをまとめたり、小技をまとめたりする予定です。
これ、誰が読むんやろ。

3
3
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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?