Python Pandas:知らないトリックと機能

Python Pandas:知らないトリックと機能

パンダは、分析、データ処理、およびデータサイエンスのための基礎ライブラリです。 膨大なオプションと深さを持つ巨大なプロジェクトです。

このチュートリアルでは、コードの読みやすさ、汎用性、速度を向上させる、あまり使用されていないが慣用的なパンダ機能をいくつか取り上げます。

PythonのPandasライブラリのコアコンセプトに慣れている場合は、この記事でこれまでに出会ったことのない秘orを見つけていただければ幸いです。 (ライブラリを使用して始めたばかりの場合は、https://pandas.pydata.org/pandas-docs/stable/10min.html [10 Minutes to Pandas]から始めるのがよいでしょう。)

:この記事の例は、Pandasバージョン0.23.2およびPython 3.6.6でテストされています。 ただし、古いバージョンでも有効である必要があります。

1. 通訳者の起動時にオプションと設定を構成する

以前にPandasのリッチhttps://pandas.pydata.org/pandas-docs/stable/options.html [オプションと設定]システムに出会ったことがあるかもしれません。

特にスクリプト環境で作業している場合、インタープリターの起動時にカスタマイズされたPandasオプションを設定することは、生産性を大幅に節約します。 `+ pd.set_option()+`を使用して、https://docs.python.org/tutorial/appendix.html#the-interactive-startup-file [Python]またはhttps:/で心のコンテンツを構成できます。/ipython.readthedocs.io/en/stable/interactive/tutorial.html#startup-files[IPython]スタートアップファイル。

オプションは、 `+ pd.set_option( 'display.max_colwidth'、25)+`などのドット表記を使用します。これは、オプションのネストされた辞書に適しています。

import pandas as pd

def start():
    options = {
        'display': {
            'max_columns': None,
            'max_colwidth': 25,
            'expand_frame_repr': False,  # Don't wrap to multiple pages
            'max_rows': 14,
            'max_seq_items': 50,         # Max length of printed sequence
            'precision': 4,
            'show_dimensions': False
        },
        'mode': {
            'chained_assignment': None   # Controls SettingWithCopyWarning
        }
    }

    for category, option in options.items():
        for op, value in option.items():
            pd.set_option(f'{category}.{op}', value)  # Python 3.6+

if __name__ == '__main__':
    start()
    del start  # Clean up namespace in the interpreter

インタープリターセッションを起動すると、起動スクリプトのすべてが実行され、オプションのスイートとともにPandasが自動的にインポートされていることがわかります。

>>>

>>> pd.__name__
'pandas'
>>> pd.get_option('display.max_rows')
14

UCI Machine Learning Repositoryでホストされているhttps://en.wikipedia.org/wiki/Abalone[abalone]のデータを使用して、スタートアップファイルで設定されたフォーマットを示しましょう。 データは、浮動小数点数の4桁の精度で14行で切り捨てられます。

>>>

>>> url = ('https://archive.ics.uci.edu/ml/'
...        'machine-learning-databases/abalone/abalone.data')
>>> cols = ['sex', 'length', 'diam', 'height', 'weight', 'rings']
>>> abalone = pd.read_csv(url, usecols=[0, 1, 2, 3, 4, 8], names=cols)

>>> abalone
     sex  length   diam  height  weight  rings
0      M   0.455  0.365   0.095  0.5140     15
1      M   0.350  0.265   0.090  0.2255      7
2      F   0.530  0.420   0.135  0.6770      9
3      M   0.440  0.365   0.125  0.5160     10
4      I   0.330  0.255   0.080  0.2050      7
5      I   0.425  0.300   0.095  0.3515      8
6      F   0.530  0.415   0.150  0.7775     20
# ...
4170   M   0.550  0.430   0.130  0.8395     10
4171   M   0.560  0.430   0.155  0.8675      8
4172   F   0.565  0.450   0.165  0.8870     11
4173   M   0.590  0.440   0.135  0.9660     10
4174   M   0.600  0.475   0.205  1.1760      9
4175   F   0.625  0.485   0.150  1.0945     10
4176   M   0.710  0.555   0.195  1.9485     12

このデータセットは、他の例でも後で表示されます。

2. Pandasのテストモジュールでおもちゃのデータ構造を作成する

Pandasのhttps://github.com/pandas-dev/pandas/blob/master/pandas/util/testing.py [+ testing +]モジュールに隠された方法は、準現実的なものをすばやく構築するための便利な関数です。シリーズとデータフレーム:

>>>

>>> import pandas.util.testing as tm
>>> tm.N, tm.K = 15, 3  # Module-level default rows/columns

>>> import numpy as np
>>> np.random.seed(444)

>>> tm.makeTimeDataFrame(freq='M').head()
                 A       B       C
2000-01-31  0.3574 -0.8804  0.2669
2000-02-29  0.3775  0.1526 -0.4803
2000-03-31  1.3823  0.2503  0.3008
2000-04-30  1.1755  0.0785 -0.1791
2000-05-31 -0.9393 -0.9039  1.1837

>>> tm.makeDataFrame().head()
                 A       B       C
nTLGGTiRHF -0.6228  0.6459  0.1251
WPBRn9jtsR -0.3187 -0.8091  1.1501
7B3wWfvuDA -1.9872 -1.0795  0.2987
yJ0BTjehH1  0.8802  0.7403 -1.2154
0luaYUYvy1 -0.9320  1.2912 -0.2907

これらは約30個あり、モジュールオブジェクトで `+ dir()+`を呼び出すことで完全なリストを見ることができます。 ここにいくつかあります:

>>>

>>> [i for i in dir(tm) if i.startswith('make')]
['makeBoolIndex',
 'makeCategoricalIndex',
 'makeCustomDataframe',
 'makeCustomIndex',
 # ...,
 'makeTimeSeries',
 'makeTimedeltaIndex',
 'makeUIntIndex',
 'makeUnicodeIndex']

これらは、ベンチマーク、アサーションのテスト、およびあまり馴染みのないPandasメソッドの実験に役立ちます。

3. アクセサメソッドを活用する

おそらく、ゲッターに似た用語*アクセサー*を聞いたことがあるでしょう(ただし、ゲッターとセッターはPythonではあまり使用されません)。 ここでの目的のために、Pandasアクセサーは、追加のメソッドへのインターフェイスとして機能するプロパティと考えることができます。

パンダシリーズには次の3つがあります。

>>>

>>> pd.Series._accessors
{'cat', 'str', 'dt'}

はい、上記の定義は一口ですので、内部について説明する前にいくつかの例を見てみましょう。

`+ .cat `はカテゴリデータ、 ` .str `は文字列(オブジェクト)データ、 ` .dt `は日時のようなデータ用です。 「 .str +」から始めましょう。生の都市/州/ZIPデータがパンダシリーズ内の単一のフィールドとしてあると想像してください。

パンダ文字列メソッドはhttps://realpython.com/numpy-array-programming/#what-is-vectorization[vectorized]です。つまり、明示的なforループなしで配列全体を操作します。

>>>

>>> addr = pd.Series([
...     'Washington, D.C. 20003',
...     'Brooklyn, NY 11211-1755',
...     'Omaha, NE 68154',
...     'Pittsburgh, PA 15211'
... ])

>>> addr.str.upper()
0     WASHINGTON, D.C. 20003
1    BROOKLYN, NY 11211-1755
2            OMAHA, NE 68154
3       PITTSBURGH, PA 15211
dtype: object

>>> addr.str.count(r'\d')  # 5 or 9-digit zip?
0    5
1    9
2    5
3    5
dtype: int64

より複雑な例として、3つの都市/州/ZIPコンポーネントをDataFrameフィールドにきちんと分離したいとします。

https://docs.python.org/howto/regex.html [正規表現]を `+ .str.extract()`に渡して、シリーズの各セルの一部を「抽出」できます。 ` .str.extract()`では、 ` .str `はアクセサであり、 ` .str.extract()+`はアクセサメソッドです。

>>>

>>> regex = (r'(?P<city>[A-Za-z ]+), '      # One or more letters
...          r'(?P<state>[A-Z]{2}) '        # 2 capital letters
...          r'(?P<zip>\d{5}(?:-\d{4})?)')  # Optional 4-digit extension
...
>>> addr.str.replace('.', '').str.extract(regex)
         city state         zip
0  Washington    DC       20003
1    Brooklyn    NY  11211-1755
2       Omaha    NE       68154
3  Pittsburgh    PA       15211

これは、メソッドチェーンと呼ばれるものも示しています。この場合、クリーンアップする `+ addr.str.replace( '。'、 '')`の結果で ` .str.extract(regex)+`が呼び出されます。ピリオドを使用して、2文字の状態の省略形を取得します。

これらのアクセサメソッドが、「+ addr.apply(re.findall、…​)+」のようなものではなく、そもそもそれらを使用する動機付けの理由としてどのように機能するかについて少し知っておくと役立ちます。

各アクセサーはそれ自体が真正なPythonクラスです。

  • + .str +`はhttps://github.com/pandas-dev/pandas/blob/3e4839301fc2927646889b194c9eb41c62b76bda/pandas/core/strings.py#L1766 [+ StringMethods +`]にマッピングされます。

  • + .dt +`はhttps://github.com/pandas-dev/pandas/blob/3e4839301fc2927646889b194c9eb41c62b76bda/pandas/core/indexes/accessors.py#L306 [+ CombinedDatetimelikeProperties +`]にマッピングされます。

  • + .cat +`はhttps://github.com/pandas-dev/pandas/blob/3e4839301fc2927646889b194c9eb41c62b76bda/pandas/core/arrays/categorical.py#L2356 [+ CategoricalAccessor +`]にルーティングします。

これらのスタンドアロンクラスは、https://github.com/pandas-dev/pandas/blob/master/pandas/core/accessor.py [+ CachedAccessor +]を使用してSeriesクラスに「アタッチ」されます。 クラスが `+ CachedAccessor +`でラップされると、ちょっとした魔法が発生します。

+ CachedAccessor +`は、「キャッシュされたプロパティ」デザインに触発されています。プロパティは、インスタンスごとに1回だけ計算され、通常の属性に置き換えられます。 これは、Pythonの記述子プロトコルの一部であるhttps://docs.python.org/reference/datamodel.html#object.get [+ . get()+` method]をオーバーロードすることで行われます。

:この仕組みの詳細については、https://docs.python.org/howto/descriptor.html [Python Descriptor HOWTO]およびhttps://www.pydannyをご覧ください。キャッシュされたプロパティデザインの.com/cached-property.html [この投稿]。 Python 3では、同様の機能を提供するhttps://docs.python.org/library/functools.html#functools.lru_cache [+ functools.lru_cache()+]も導入しました。 https://github.com/aio-libs/aiohttp/blob/master/aiohttp/_helpers.pyx [+ aiohttp +]パッケージなど、このパターンのいたるところに例があります。

2番目のアクセサーである `+ .dt `は、日時のようなデータ用です。 技術的にはPandasの ` DatetimeIndex `に属し、シリーズで呼び出された場合、最初に ` DatetimeIndex +`に変換されます。

>>>

>>> daterng = pd.Series(pd.date_range('2017', periods=9, freq='Q'))
>>> daterng
0   2017-03-31
1   2017-06-30
2   2017-09-30
3   2017-12-31
4   2018-03-31
5   2018-06-30
6   2018-09-30
7   2018-12-31
8   2019-03-31
dtype: datetime64[ns]

>>>  daterng.dt.day_name()
0      Friday
1      Friday
2    Saturday
3      Sunday
4    Saturday
5    Saturday
6      Sunday
7      Monday
8      Sunday
dtype: object

>>> # Second-half of year only
>>> daterng[daterng.dt.quarter > 2]
2   2017-09-30
3   2017-12-31
6   2018-09-30
7   2018-12-31
dtype: datetime64[ns]

>>> daterng[daterng.dt.is_year_end]
3   2017-12-31
7   2018-12-31
dtype: datetime64[ns]

3番目のアクセサである `+ .cat +`はカテゴリデータ専用であり、リンクで間もなく表示されます:#5-use-categorical-data-to-save-on-time-and-space [own section] 。

4. コンポーネント列からDatetimeIndexを作成する

上記の「+ daterng 」のように日時のようなデータについて言えば、一緒に日付または日時を形成する複数のコンポーネント列からパンダの「 DatetimeIndex +」を作成することができます。

>>>

>>> from itertools import product
>>> datecols = ['year', 'month', 'day']

>>> df = pd.DataFrame(list(product([2017, 2016], [1, 2], [1, 2, 3])),
...                   columns=datecols)
>>> df['data'] = np.random.randn(len(df))
>>> df
    year  month  day    data
0   2017      1    1 -0.0767
1   2017      1    2 -1.2798
2   2017      1    3  0.4032
3   2017      2    1  1.2377
4   2017      2    2 -0.2060
5   2017      2    3  0.6187
6   2016      1    1  2.3786
7   2016      1    2 -0.4730
8   2016      1    3 -2.1505
9   2016      2    1 -0.6340
10  2016      2    2  0.7964
11  2016      2    3  0.0005

>>> df.index = pd.to_datetime(df[datecols])
>>> df.head()
            year  month  day    data
2017-01-01  2017      1    1 -0.0767
2017-01-02  2017      1    2 -1.2798
2017-01-03  2017      1    3  0.4032
2017-02-01  2017      2    1  1.2377
2017-02-02  2017      2    2 -0.2060

最後に、古い個々の列をドロップして、シリーズに変換できます。

>>>

>>> df = df.drop(datecols, axis=1).squeeze()
>>> df.head()
2017-01-01   -0.0767
2017-01-02   -1.2798
2017-01-03    0.4032
2017-02-01    1.2377
2017-02-02   -0.2060
Name: data, dtype: float64

>>> df.index.dtype_str
'datetime64[ns]

DataFrameを渡すことの背後にある直感は、DataFrameは列名がキーであり、個々の列(シリーズ)が辞書の値であるPython辞書に似ているということです。 そのため、この場合、「+ pd.to_datetime(df [datecols] .to_dict(orient = 'list'))」も機能します。 これは、Pythonの ` datetime.datetime `の構造を反映しており、 ` datetime.datetime(year = 2000、month = 1、day = 15、hour = 10)+`などのキーワード引数を渡します。

5. カテゴリデータを使用して時間とスペースを節約する

パンダの強力な機能の1つは、「+ Categorical +」dtypeです。

RAM内のギガバイトのデータを常に操作しているわけではない場合でも、大規模なDataFrameでの簡単な操作が数秒以上ハングアップする場合があります。

パンダの + object + dtypeは、多くの場合、カテゴリデータへの変換の優れた候補です。 ( + object +`はPythonの `+ str +、異種データ型、または「その他」型のコンテナです。)文字列はメモリのかなりのスペースを占有します。

>>>

>>> colors = pd.Series([
...     'periwinkle',
...     'mint green',
...     'burnt orange',
...     'periwinkle',
...     'burnt orange',
...     'rose',
...     'rose',
...     'mint green',
...     'rose',
...     'navy'
... ])
...
>>> import sys
>>> colors.apply(sys.getsizeof)
0    59
1    59
2    61
3    59
4    61
5    53
6    53
7    59
8    53
9    53
dtype: int64

注意: `+ sys.getsizeof()`を使用して、シリーズの個々の値が占有するメモリを表示しました。 これらは、最初はオーバーヘッドがあるPythonオブジェクトであることに注意してください。 ( ` sys.getsizeof( '')+`は49バイトを返します。)

`+ colors.memory_usage()`もあります。これはメモリ使用量を合計し、基礎となるNumPy配列の ` .nbytes +`属性に依存します。 これらの詳細に行き詰まりすぎないようにしてください。重要なのは、次に示すように、型変換から生じる相対的なメモリ使用量です。

さて、上記のユニークな色を使用して、それぞれをより少ないスペース占有整数にマッピングできたらどうでしょうか? 以下はその単純な実装です。

>>>

>>> mapper = {v: k for k, v in enumerate(colors.unique())}
>>> mapper
{'periwinkle': 0, 'mint green': 1, 'burnt orange': 2, 'rose': 3, 'navy': 4}

>>> as_int = colors.map(mapper)
>>> as_int
0    0
1    1
2    2
3    0
4    2
5    3
6    3
7    1
8    3
9    4
dtype: int64

>>> as_int.apply(sys.getsizeof)
0    24
1    28
2    28
3    24
4    28
5    28
6    28
7    28
8    28
9    28
dtype: int64

:これと同じことを行う別の方法は、Pandasの `+ pd.factorize(colors)+`を使用することです。

>>>

>>> pd.factorize(colors)[0]
array([0, 1, 2, 0, 2, 3, 3, 1, 3, 4])

いずれにせよ、オブジェクトを列挙型(カテゴリ変数)としてエンコードしています。

完全な文字列が + object + dtypeで使用される場合と比較して、メモリ使用量が半分に削減されていることがすぐにわかります。

link:#3-take-advantage-of-accessor-methods [accessors]のセクションで前に、 + .cat +(カテゴリ)アクセサーについて言及しました。 上記の + mapper +`は、Pandasの `+ Categorical + dtypeで内部的に起こっていることの大まかな説明です。

_ 「「+ Categorical 」のメモリ使用量は、カテゴリの数とデータの長さに比例します。 対照的に、 ` object +` dtypeはデータの長さの定数倍です。」 https://pandas.pydata.org/pandas-docs/stable/categorical.html#memory-usage [(ソース)] _

上記の `+ colors +`では、すべての一意の値(カテゴリ)に対して2つの値の比率があります。

>>>

>>> len(colors)/colors.nunique()
2.0

結果として、 `+ Categorical +`に変換することによるメモリの節約は良いですが、それほど大きくありません:

>>>

>>> # Not a huge space-saver to encode as Categorical
>>> colors.memory_usage(index=False, deep=True)
650
>>> colors.astype('category').memory_usage(index=False, deep=True)
495

ただし、多くのデータと少数の一意の値(人口統計またはアルファベットのテストスコアのデータを考える)で上記の割合を吹き飛ばすと、必要なメモリの削減は10倍以上になります。

>>>

>>> manycolors = colors.repeat(10)
>>> len(manycolors)/manycolors.nunique()  # Much greater than 2.0x
20.0

>>> manycolors.memory_usage(index=False, deep=True)
6500
>>> manycolors.astype('category').memory_usage(index=False, deep=True)
585

ボーナスは、計算効率も向上することです。カテゴリ型の「+ Series 」の場合、文字列操作https://pandas.pydata.org/pandas-docs/stable/text.html [は、。catで実行されます。 `+ Series +`の各元の要素ではなく、categories + `属性]。

つまり、操作は一意のカテゴリごとに1回実行され、結果は値にマップされます。 カテゴリデータには、カテゴリを操作するための属性とメソッドへのウィンドウである `+ .cat +`アクセサがあります。

>>>

>>> ccolors = colors.astype('category')
>>> ccolors.cat.categories
Index(['burnt orange', 'mint green', 'navy', 'periwinkle', 'rose'], dtype='object')

実際、手動で行った上記の例に似たものを再現できます。

>>>

>>> ccolors.cat.codes
0    3
1    1
2    0
3    3
4    0
5    4
6    4
7    1
8    4
9    2
dtype: int8

以前の手動出力を正確に模倣するために必要なことは、コードを並べ替えることだけです。

>>>

>>> ccolors.cat.reorder_categories(mapper).cat.codes
0    0
1    1
2    2
3    0
4    2
5    3
6    3
7    1
8    3
9    4
dtype: int8

dtypeはNumPyの `+ int8 `であることに注意してください。これはhttps://docs.scipy.org/doc/numpy-1.10.0/user/basics.types.html[8ビット符号付き整数]であり、 -127から128。 (メモリ内の値を表すために必要なのは1バイトだけです。 64ビットの符号付き ` ints `は、メモリ使用量の点で過剰です。)荒削りの例では、デフォルトで ` int64 +`データが生成されますが、Pandasはカテゴリデータを可能な限り最小の数値dtypeにダウンキャストするのに十分スマートです。

`+ .cat +`の属性のほとんどは、基礎となるカテゴリ自体の表示と操作に関連しています。

>>>

>>> [i for i in dir(ccolors.cat) if not i.startswith('_')]
['add_categories',
 'as_ordered',
 'as_unordered',
 'categories',
 'codes',
 'ordered',
 'remove_categories',
 'remove_unused_categories',
 'rename_categories',
 'reorder_categories',
 'set_categories']

ただし、いくつかの注意事項があります。 カテゴリデータは一般に柔軟性が低くなります。 たとえば、以前に見えなかった値を挿入する場合、最初にこの値を `+ .categories +`コンテナに追加する必要があります。

>>>

>>> ccolors.iloc[5] = 'a new color'
# ...
ValueError: Cannot setitem on a Categorical with a new category,
set the categories first

>>> ccolors = ccolors.cat.add_categories(['a new color'])
>>> ccolors.iloc[5] = 'a new color'  # No more ValueError

新しい計算を導出するのではなく、値を設定したりデータを再形成したりする予定がある場合、 `+ Categorical +`タイプはあまり機敏ではありません。

6. 反復によるGroupbyオブジェクトのイントロスペクト

`+ df.groupby( 'x')`を呼び出すと、結果のPandasの ` groupby +`オブジェクトが少し不透明になります。 このオブジェクトは遅延的にインスタンス化され、それ自体に意味のある表現はありません。

link:#1-configure-options-settings-at-interpreter-startup [例1]からアワビデータセットを使用してデモンストレーションできます。

>>>

>>> abalone['ring_quartile'] = pd.qcut(abalone.rings, q=4, labels=range(1, 5))
>>> grouped = abalone.groupby('ring_quartile')

>>> grouped
<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x11c1169b0>

さて、これで `+ groupby +`オブジェクトができましたが、これは何であり、どのように見ることができますか?

`+ grouped.apply(func)`のようなものを呼び出す前に、 ` groupby +`オブジェクトが反復可能であるという事実を利用できます。

>>>

>>> help(grouped.__iter__)

        Groupby iterator

        Returns
        -------
        Generator yielding sequence of (name, subsetted object)
        for each group

`+ grouped . iter ()`によって生成される各「もの」は、 `(name、サブセット化されたオブジェクト)`のタプルです。ここで、 ` name +`はグループ化する列の値、 + subsetted object + `は、指定したグループ化条件に基づいた元のDataFrameのサブセットであるDataFrameです。 つまり、データはグループごとに分割されます。

>>>

>>> for idx, frame in grouped:
...     print(f'Ring quartile: {idx}')
...     print('-' * 16)
...     print(frame.nlargest(3, 'weight'), end='\n\n')
...
Ring quartile: 1
----------------
     sex  length   diam  height  weight  rings ring_quartile
2619   M   0.690  0.540   0.185  1.7100      8             1
1044   M   0.690  0.525   0.175  1.7005      8             1
1026   M   0.645  0.520   0.175  1.5610      8             1

Ring quartile: 2
----------------
     sex  length  diam  height  weight  rings ring_quartile
2811   M   0.725  0.57   0.190  2.3305      9             2
1426   F   0.745  0.57   0.215  2.2500      9             2
1821   F   0.720  0.55   0.195  2.0730      9             2

Ring quartile: 3
----------------
     sex  length  diam  height  weight  rings ring_quartile
1209   F   0.780  0.63   0.215   2.657     11             3
1051   F   0.735  0.60   0.220   2.555     11             3
3715   M   0.780  0.60   0.210   2.548     11             3

Ring quartile: 4
----------------
     sex  length   diam  height  weight  rings ring_quartile
891    M   0.730  0.595    0.23  2.8255     17             4
1763   M   0.775  0.630    0.25  2.7795     12             4
165    M   0.725  0.570    0.19  2.5500     14             4

関連して、 `+ groupby `オブジェクトには ` .groups `とグループゲッター、 ` .get_group()+`もあります:

>>>

>>> grouped.groups.keys()
dict_keys([1, 2, 3, 4])

>>> grouped.get_group(2).head()
   sex  length   diam  height  weight  rings ring_quartile
2    F   0.530  0.420   0.135  0.6770      9             2
8    M   0.475  0.370   0.125  0.5095      9             2
19   M   0.450  0.320   0.100  0.3810      9             2
23   F   0.550  0.415   0.135  0.7635      9             2
39   M   0.355  0.290   0.090  0.3275      9             2

これにより、実行している操作が目的の操作であることをもう少し確信できるようになります。

>>>

>>> grouped['height', 'weight'].agg(['mean', 'median'])
               height         weight
                 mean median    mean  median
ring_quartile
1              0.1066  0.105  0.4324  0.3685
2              0.1427  0.145  0.8520  0.8440
3              0.1572  0.155  1.0669  1.0645
4              0.1648  0.165  1.1149  1.0655

「+ grouped +」でどのような計算を実行しても、単一のPandasメソッドであろうとカスタムビルド関数であろうと、これらの「サブフレーム」のそれぞれはその呼び出し可能オブジェクトへの引数として1つずつ渡されます。 これが、「分割適用結合」という用語の由来です。グループごとにデータを分割し、グループごとの計算を実行し、集約された方法で再結合します。

グループが実際にどのように見えるかを正確に視覚化できない場合は、単にグループを繰り返していくつかを印刷するだけで非常に便利です。

7. メンバーシップビニングにこのマッピングトリックを使用する

たとえば、シリーズと対応する「マッピングテーブル」があり、各値がマルチメンバーグループに属しているか、グループがまったくないとします。

>>>

>>> countries = pd.Series([
...     'United States',
...     'Canada',
...     'Mexico',
...     'Belgium',
...     'United Kingdom',
...     'Thailand'
... ])
...
>>> groups = {
...     'North America': ('United States', 'Canada', 'Mexico', 'Greenland'),
...     'Europe': ('France', 'Germany', 'United Kingdom', 'Belgium')
... }

つまり、「+ countries +」を次の結果にマッピングする必要があります。

>>>

0    North America
1    North America
2    North America
3           Europe
4           Europe
5            other
dtype: object

ここで必要なのは、Pandasの `+ pd.cut()`に似た関数ですが、カテゴリメンバーシップに基づくビニング用です。 リンクですでに見た ` pd.Series.map()+`を使用できます:#5-use-categorical-data-to-save-on-time-and-space [例#5]、模倣するこの:

from typing import Any

def membership_map(s: pd.Series, groups: dict,
                   fillvalue: Any=-1) -> pd.Series:
    # Reverse & expand the dictionary key-value pairs
    groups = {x: k for k, v in groups.items() for x in v}
    return s.map(groups).fillna(fillvalue)

これは、 `+ countries `の国ごとに ` groups +`を介してネストされたPythonループよりも大幅に高速です。

これがテストドライブです。

>>>

>>> membership_map(countries, groups, fillvalue='other')
0    North America
1    North America
2    North America
3           Europe
4           Europe
5            other
dtype: object

ここで何が起こっているのかを分析しましょう。 (補足:Pythonのデバッガーhttps://realpython.com/python-debugging-pdb/[+ pdb +]を使用して関数のスコープに足を踏み入れると、どの変数が関数のローカルにあるかを調べるのに最適な場所です。)

目的は、「+ groups 」の各グループを整数にマッピングすることです。 ただし、 ` Series.map()`は ` 'ab' +`を認識しません。各グループの各文字が整数にマッピングされた、分割バージョンが必要です。 これはhttps://docs.python.org/tutorial/datastructures.html#dictionaries[dictionary comprehension]が行っていることです:

>>>

>>> groups = dict(enumerate(('ab', 'cd', 'xyz')))
>>> {x: k for k, v in groups.items() for x in v}
{'a': 0, 'b': 0, 'c': 1, 'd': 1, 'x': 2, 'y': 2, 'z': 2}

この辞書を `+ s.map()+`に渡して、その値を対応するグループインデックスにマッピングまたは「変換」することができます。

8. パンダがブール演算子を使用する方法を理解する

Pythonのhttps://docs.python.org/reference/expressions.html#operator-precedence[operator precedence]に精通している場合があります。ここで、「+ and 」、「 not 」、および「 or 」の優先順位は、 「 <」、「 ⇐ 」、「> 」、「> = 」、「!= 」、「 == 」などの算術演算子。 以下の2つのステートメントを検討してください。ここで、「 <」と「> 」は「 and +」演算子よりも優先順位が高くなります。

>>>

>>> # Evaluates to "False and True"
>>> 4 < 3 and 5 > 4
False

>>> # Evaluates to 4 < 5 > 4
>>> 4 < (3 and 5) > 4
True

:特にパンダ関連ではありませんが、短絡評価のため、「+ 3 and 5+」は「5」と評価されます。

_ 「短絡演算子の戻り値は、最後に評価された引数です。」 https://docs.python.org/3/tutorial/datastructures.html#more-on-conditions [(ソース)] _

パンダ(およびパンダが構築されているNumPy)は、「+ and 」、「 or 」、または「 not 」を使用しません。 代わりに、それぞれ「&」、「 | 」、および「〜+」を使用します。これらは通常の真正なPythonビット演算子です。

これらの演算子は、パンダによって「発明された」ものではありません。 むしろ、 + | +、および `〜`は、算術演算子よりも(低いよりも)高い優先順位を持つ有効なPython組み込み演算子です。 (パンダは、「+ | 」演算子にマッピングされる「 . ror ()+」などのダンダーメソッドをオーバーライドします。)詳細を犠牲にするために、「ビット単位」をパンダとNumPyに関連する「要素単位」と考えることができます。

>>>

>>> pd.Series([True, True, False]) & pd.Series([True, False, False])
0     True
1    False
2    False
dtype: bool

この概念を完全に理解することは有益です。 範囲のようなシリーズがあるとしましょう:

>>>

>>> s = pd.Series(range(10))

ある時点でこの例外が発生したのを見たことがあると思います。

>>>

>>> s % 2 == 0 & s > 3
ValueError: The truth value of a Series is ambiguous.
Use a.empty, a.bool(), a.item(), a.any() or a.all().

ここで何が起こっていますか? 式を括弧で段階的にバインドして、Pythonがこの式を段階的に展開する方法を説明すると便利です。

s % 2 == 0 & s > 3                      # Same as above, original expression
(s % 2) == 0 & s > 3                    # Modulo is most tightly binding here
(s % 2) == (0 & s) > 3                  # Bitwise-and is second-most-binding
(s % 2) == (0 & s) and (0 & s) > 3      # Expand the statement
((s % 2) == (0 & s)) and ((0 & s) > 3)  # The `and` operator is least-binding

式「+ s%2 == 0&s> 3+」は、「((s%2)==(0&s))および((0&s)> 3 ) `。 これはhttps://docs.python.org/reference/expressions.html#comparisons[expansion]と呼ばれます: `+ x <y ⇐ z `は ` x <yおよびy ⇐ z +`と同等です。

さて、そこで停止し、これをパンダ語に戻しましょう。 「+ left 」と「 right +」と呼ぶ2つのパンダシリーズがあります。

>>>

>>> left = (s % 2) == (0 & s)
>>> right = (0 & s) > 3
>>> left and right  # This will raise the same ValueError

次のように、「+ left and right 」という形式のステートメントは、「 left 」と「 right +」の両方をテストする真理値であることを知っています。

>>>

>>> bool(left) and bool(right)

問題は、パンダの開発者がシリーズ全体の真理値(真実性)を意図的に確立していないことです。 シリーズは真ですか、それとも偽ですか? 知るか? 結果はあいまいです:

>>>

>>> bool(s)
ValueError: The truth value of a Series is ambiguous.
Use a.empty, a.bool(), a.item(), a.any() or a.all().

意味のある唯一の比較は、要素ごとの比較です。 そのため、算術演算子が関係している場合、https://pandas.pydata.org/pandas-docs/stable/indexing.html#boolean-indexing [括弧が必要です]:

>>>

>>> (s % 2 == 0) & (s > 3)
0    False
1    False
2    False
3    False
4     True
5    False
6     True
7    False
8     True
9    False
dtype: bool

要するに、ブールインデックスが付いた上記の `+ ValueError +`がポップアップ表示された場合、おそらく最初にすべきことは、必要な括弧を振りかけることです。

9. クリップボードからデータを読み込む

Excelやhttps://realpython.com/setting-up-sublime-text-3-for-full-stack-python-development/[Sublime Text]などの場所からパンダにデータを転送する必要があるのは一般的な状況ですデータ構造。 理想的には、データをファイルに保存し、その後ファイルをPandasに読み込むという中間ステップを実行せずに、これを実行する必要があります。

https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_clipboard.html [+ pd.read_clipboard()+]を使用して、コンピューターのクリップボードデータバッファーからDataFramesを読み込むことができます。 そのキーワード引数はhttps://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_table.html [+ pd.read_table()+]に渡されます。

これにより、構造化テキストを直接DataFrameまたはSeriesにコピーできます。 Excelでは、データは次のようになります。

Excel Clipboard Data、width = 576、height = 216

そのプレーンテキスト表現(たとえば、テキストエディター)は次のようになります。

a   b           c       d
0   1           inf     1/1/00
2   7.389056099 N/A     5-Jan-13
4   54.59815003 nan     7/24/18
6   403.4287935 None    NaT

上記のプレーンテキストを強調表示してコピーし、 `+ pd.read_clipboard()+`を呼び出します。

>>>

>>> df = pd.read_clipboard(na_values=[None], parse_dates=['d'])
>>> df
   a         b    c          d
0  0    1.0000  inf 2000-01-01
1  2    7.3891  NaN 2013-01-05
2  4   54.5982  NaN 2018-07-24
3  6  403.4288  NaN        NaT

>>> df.dtypes
a             int64
b           float64
c           float64
d    datetime64[ns]
dtype: object

10. Pandasオブジェクトを圧縮形式に直接書き込む

これはリストを締めくくるのに短くて甘いです。 Pandasバージョン0.21.0では、非圧縮ファイルをメモリに隠して変換するのではなく、Pandasオブジェクトをgzip、bz2、zip、またはxz圧縮に直接書き込むことができます。 リンクの「+ abalone +」データを使用した例を次に示します:#1-configure-options-settings-at-interpreter-startup [trick#1]:

abalone.to_json('df.json.gz', orient='records',
                lines=True, compression='gzip')

この場合、サイズの違いは11.6xです。

>>>

>>> import os.path
>>> abalone.to_json('df.json', orient='records', lines=True)
>>> os.path.getsize('df.json')/os.path.getsize('df.json.gz')
11.603035760226396

このリストに追加しますか? 私たちに知らせて

うまくいけば、このリストからいくつかの便利なトリックを選んで、Pandasコードの読みやすさ、汎用性、パフォーマンスを向上させることができました。

ここに記載されていないものがありましたら、コメントまたはhttps://gist.github.com/[GitHub Gist]に提案を残してください。 このリストに喜んで追加し、期限が来たらクレジットを提供します。