データ構造入門#

まず、pandas の基本的なデータ構造について、手早く、包括的ではない概要から始めます。データ型、インデックス、軸ラベル、およびアラインメントに関する基本的な動作は、すべてのオブジェクトに適用されます。開始するには、NumPy をインポートし、pandas を名前空間にロードします。

In [1]: import numpy as np

In [2]: import pandas as pd

基本的に、データのアラインメントは本質的です。ラベルとデータの間のリンクは、ユーザーが明示的にそうしない限り、壊れることはありません。

まず、データ構造の簡単な紹介を行い、その後、さまざまな機能とメソッドのカテゴリを個別のセクションで検討します。

Series#

Series は、任意のデータ型 (整数、文字列、浮動小数点数、Python オブジェクトなど) を保持できる 1 次元ラベル付き配列です。軸ラベルは、まとめて インデックス と呼ばれます。Series を作成する基本的な方法は、次を呼び出すことです。

s = pd.Series(data, index=index)

ここで、data はさまざまなものにできます。

  • Python の dict

  • ndarray

  • スカラー値 (5 など)

渡された index は軸ラベルのリストです。したがって、これは data が何か によって、いくつかのケースに分かれます。

ndarray から

data が ndarray の場合、indexdata と同じ長さである必要があります。インデックスが渡されない場合、[0, ..., len(data) - 1] の値を持つインデックスが作成されます。

In [3]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [4]: s
Out[4]: 
a    0.469112
b   -0.282863
c   -1.509059
d   -1.135632
e    1.212112
dtype: float64

In [5]: s.index
Out[5]: Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

In [6]: pd.Series(np.random.randn(5))
Out[6]: 
0   -0.173215
1    0.119209
2   -1.044236
3   -0.861849
4   -2.104569
dtype: float64

pandas は一意でないインデックス値をサポートしています。重複インデックス値をサポートしない操作が試行された場合、その時点で例外が発生します。

dict から

Series は dict からインスタンス化できます。

In [7]: d = {"b": 1, "a": 0, "c": 2}

In [8]: pd.Series(d)
Out[8]: 
b    1
a    0
c    2
dtype: int64

インデックスが渡された場合、インデックス内のラベルに対応するデータ内の値が抽出されます。

In [9]: d = {"a": 0.0, "b": 1.0, "c": 2.0}

In [10]: pd.Series(d)
Out[10]: 
a    0.0
b    1.0
c    2.0
dtype: float64

In [11]: pd.Series(d, index=["b", "c", "d", "a"])
Out[11]: 
b    1.0
c    2.0
d    NaN
a    0.0
dtype: float64

NaN (Not a Number) は、pandas で使用される標準の欠損データマーカーです。

スカラー値から

data がスカラー値の場合、インデックスを指定する必要があります。値は、index の長さに一致するように繰り返されます。

In [12]: pd.Series(5.0, index=["a", "b", "c", "d", "e"])
Out[12]: 
a    5.0
b    5.0
c    5.0
d    5.0
e    5.0
dtype: float64

Series は ndarray のようなもの#

Seriesndarray と非常に似た動作をし、ほとんどの NumPy 関数の有効な引数です。ただし、スライスなどの操作では、インデックスもスライスされます。

In [13]: s.iloc[0]
Out[13]: 0.4691122999071863

In [14]: s.iloc[:3]
Out[14]: 
a    0.469112
b   -0.282863
c   -1.509059
dtype: float64

In [15]: s[s > s.median()]
Out[15]: 
a    0.469112
e    1.212112
dtype: float64

In [16]: s.iloc[[4, 3, 1]]
Out[16]: 
e    1.212112
d   -1.135632
b   -0.282863
dtype: float64

In [17]: np.exp(s)
Out[17]: 
a    1.598575
b    0.753623
c    0.221118
d    0.321219
e    3.360575
dtype: float64

s.iloc[[4, 3, 1]] のような配列ベースのインデックスについては、インデックスに関するセクションで説明します。

NumPy 配列と同様に、pandas の Series は、単一の dtype を持ちます。

In [18]: s.dtype
Out[18]: dtype('float64')

これは多くの場合、NumPy の dtype です。ただし、pandas およびサードパーティライブラリは、いくつかの場所で NumPy の型システムを拡張します。その場合、dtype は ExtensionDtype になります。pandas 内の例としては、カテゴリカルデータNullable 整数データ型があります。詳細については、dtypes を参照してください。

Series をバックアップする実際の配列が必要な場合は、Series.array を使用します。

In [19]: s.array
Out[19]: 
<NumpyExtensionArray>
[ 0.4691122999071863, -0.2828633443286633, -1.5090585031735124,
 -1.1356323710171934,  1.2121120250208506]
Length: 5, dtype: float64

配列へのアクセスは、インデックスなしで何らかの操作を実行する必要がある場合 (たとえば、自動アラインメントを無効にする場合) に役立ちます。

Series.array は常に ExtensionArray になります。簡単に言えば、ExtensionArray は、numpy.ndarray のような 1 つ以上の具体的な配列を薄くラップしたものです。pandas は、ExtensionArray を取得し、Series または DataFrame の列に格納する方法を認識しています。詳細については、dtypes を参照してください。

Series は ndarray のようなものですが、実際の ndarray が必要な場合は、Series.to_numpy() を使用します。

In [20]: s.to_numpy()
Out[20]: array([ 0.4691, -0.2829, -1.5091, -1.1356,  1.2121])

SeriesExtensionArray でバックアップされている場合でも、Series.to_numpy() は NumPy ndarray を返します。

Series は dict のようなもの#

Series は、インデックスラベルで値を取得および設定できる点で、固定サイズの dict のようなものでもあります。

In [21]: s["a"]
Out[21]: 0.4691122999071863

In [22]: s["e"] = 12.0

In [23]: s
Out[23]: 
a     0.469112
b    -0.282863
c    -1.509059
d    -1.135632
e    12.000000
dtype: float64

In [24]: "e" in s
Out[24]: True

In [25]: "f" in s
Out[25]: False

ラベルがインデックスに含まれていない場合、例外が発生します。

In [26]: s["f"]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/core/indexes/base.py:3805, in Index.get_loc(self, key)
   3804 try:
-> 3805     return self._engine.get_loc(casted_key)
   3806 except KeyError as err:

File index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()

File index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/hashtable_class_helper.pxi:7081, in pandas._libs.hashtable.PyObjectHashTable.get_item()

File pandas/_libs/hashtable_class_helper.pxi:7089, in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'f'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[26], line 1
----> 1 s["f"]

File ~/work/pandas/pandas/pandas/core/series.py:1112, in Series.__getitem__(self, key)
   1109     return self._values[key]
   1111 elif key_is_scalar:
-> 1112     return self._get_value(key)
   1114 # Convert generator to list before going through hashable part
   1115 # (We will iterate through the generator there to check for slices)
   1116 if is_iterator(key):

File ~/work/pandas/pandas/pandas/core/series.py:1228, in Series._get_value(self, label, takeable)
   1225     return self._values[label]
   1227 # Similar to Index.get_value, but we do not fall back to positional
-> 1228 loc = self.index.get_loc(label)
   1230 if is_integer(loc):
   1231     return self._values[loc]

File ~/work/pandas/pandas/pandas/core/indexes/base.py:3812, in Index.get_loc(self, key)
   3807     if isinstance(casted_key, slice) or (
   3808         isinstance(casted_key, abc.Iterable)
   3809         and any(isinstance(x, slice) for x in casted_key)
   3810     ):
   3811         raise InvalidIndexError(key)
-> 3812     raise KeyError(key) from err
   3813 except TypeError:
   3814     # If we have a listlike key, _check_indexing_error will raise
   3815     #  InvalidIndexError. Otherwise we fall through and re-raise
   3816     #  the TypeError.
   3817     self._check_indexing_error(key)

KeyError: 'f'

Series.get() メソッドを使用すると、欠落しているラベルは None または指定されたデフォルトを返します。

In [27]: s.get("f")

In [28]: s.get("f", np.nan)
Out[28]: nan

これらのラベルは、属性を使用してアクセスすることもできます。

Series によるベクトル化された操作とラベルのアラインメント#

生の NumPy 配列を操作する場合、通常、値を 1 つずつループ処理する必要はありません。pandas の Series を操作する場合も同様です。Series は、ndarray を予期するほとんどの NumPy メソッドにも渡すことができます。

In [29]: s + s
Out[29]: 
a     0.938225
b    -0.565727
c    -3.018117
d    -2.271265
e    24.000000
dtype: float64

In [30]: s * 2
Out[30]: 
a     0.938225
b    -0.565727
c    -3.018117
d    -2.271265
e    24.000000
dtype: float64

In [31]: np.exp(s)
Out[31]: 
a         1.598575
b         0.753623
c         0.221118
d         0.321219
e    162754.791419
dtype: float64

Series と ndarray の主な違いは、Series 間の操作では、ラベルに基づいてデータが自動的にアラインメントされることです。したがって、関与する Series が同じラベルを持っているかどうかを考慮せずに計算を記述できます。

In [32]: s.iloc[1:] + s.iloc[:-1]
Out[32]: 
a         NaN
b   -0.565727
c   -3.018117
d   -2.271265
e         NaN
dtype: float64

アラインメントされていないSeries間の演算の結果は、関係するインデックスの和集合になります。あるSeriesまたは別のSeriesでラベルが見つからない場合、結果は欠損値NaNとしてマークされます。明示的なデータアラインメントを行わずにコードを記述できることで、インタラクティブなデータ分析や研究において非常に大きな自由度と柔軟性が得られます。pandasのデータ構造に組み込まれたデータアラインメント機能は、ラベル付きデータを扱うための関連ツールの大部分とは一線を画しています。

一般的に、異なるインデックスを持つオブジェクト間の演算のデフォルトの結果をインデックスの和集合にすることを選択したのは、情報の損失を避けるためです。データが欠損しているとしても、インデックスラベルを持つことは、通常、計算の一部として重要な情報です。もちろん、dropna関数を使用して、欠損データを持つラベルを削除することもできます。

名前属性#

Seriesには、name属性もあります。

In [33]: s = pd.Series(np.random.randn(5), name="something")

In [34]: s
Out[34]: 
0   -0.494929
1    1.071804
2    0.721555
3   -0.706771
4   -1.039575
Name: something, dtype: float64

In [35]: s.name
Out[35]: 'something'

Seriesnameは、多くの場合、特にDataFrameから単一の列を選択する場合に自動的に割り当てられ、nameには列ラベルが割り当てられます。

Seriesの名前を変更するには、pandas.Series.rename()メソッドを使用します。

In [36]: s2 = s.rename("different")

In [37]: s2.name
Out[37]: 'different'

ss2は異なるオブジェクトを参照することに注意してください。

DataFrame#

DataFrameは、潜在的に異なる型の列を持つ2次元のラベル付きデータ構造です。これは、スプレッドシートやSQLテーブル、またはSeriesオブジェクトの辞書のように考えることができます。一般的に、最もよく使用されるpandasオブジェクトです。Seriesと同様に、DataFrameはさまざまな種類の入力を受け付けます。

データとともに、オプションでindex(行ラベル)とcolumns(列ラベル)引数を渡すことができます。インデックスや列を渡すと、結果のDataFrameのインデックスや列が保証されます。したがって、Seriesの辞書に特定のインデックスを加えると、渡されたインデックスに一致しないすべてのデータが破棄されます。

軸ラベルが渡されない場合、常識的なルールに基づいて入力データから構築されます。

Seriesまたは辞書の辞書から#

結果のindexは、さまざまなSeriesのインデックスの和集合になります。ネストされた辞書がある場合、これらは最初にSeriesに変換されます。列が渡されない場合、列は辞書のキーの順序付きリストになります。

In [38]: d = {
   ....:     "one": pd.Series([1.0, 2.0, 3.0], index=["a", "b", "c"]),
   ....:     "two": pd.Series([1.0, 2.0, 3.0, 4.0], index=["a", "b", "c", "d"]),
   ....: }
   ....: 

In [39]: df = pd.DataFrame(d)

In [40]: df
Out[40]: 
   one  two
a  1.0  1.0
b  2.0  2.0
c  3.0  3.0
d  NaN  4.0

In [41]: pd.DataFrame(d, index=["d", "b", "a"])
Out[41]: 
   one  two
d  NaN  4.0
b  2.0  2.0
a  1.0  1.0

In [42]: pd.DataFrame(d, index=["d", "b", "a"], columns=["two", "three"])
Out[42]: 
   two three
d  4.0   NaN
b  2.0   NaN
a  1.0   NaN

行と列のラベルには、それぞれindex属性とcolumns属性にアクセスすることでアクセスできます。

特定の列のセットがデータの辞書とともに渡されると、渡された列は辞書のキーをオーバーライドします。

In [43]: df.index
Out[43]: Index(['a', 'b', 'c', 'd'], dtype='object')

In [44]: df.columns
Out[44]: Index(['one', 'two'], dtype='object')

ndarray/リストの辞書から#

すべてのndarrayは同じ長さを共有する必要があります。インデックスが渡される場合は、配列と同じ長さである必要もあります。インデックスが渡されない場合、結果はrange(n)になります。ここで、nは配列の長さです。

In [45]: d = {"one": [1.0, 2.0, 3.0, 4.0], "two": [4.0, 3.0, 2.0, 1.0]}

In [46]: pd.DataFrame(d)
Out[46]: 
   one  two
0  1.0  4.0
1  2.0  3.0
2  3.0  2.0
3  4.0  1.0

In [47]: pd.DataFrame(d, index=["a", "b", "c", "d"])
Out[47]: 
   one  two
a  1.0  4.0
b  2.0  3.0
c  3.0  2.0
d  4.0  1.0

構造化またはレコード配列から#

このケースは、配列の辞書と同じように処理されます。

In [48]: data = np.zeros((2,), dtype=[("A", "i4"), ("B", "f4"), ("C", "a10")])

In [49]: data[:] = [(1, 2.0, "Hello"), (2, 3.0, "World")]

In [50]: pd.DataFrame(data)
Out[50]: 
   A    B         C
0  1  2.0  b'Hello'
1  2  3.0  b'World'

In [51]: pd.DataFrame(data, index=["first", "second"])
Out[51]: 
        A    B         C
first   1  2.0  b'Hello'
second  2  3.0  b'World'

In [52]: pd.DataFrame(data, columns=["C", "A", "B"])
Out[52]: 
          C  A    B
0  b'Hello'  1  2.0
1  b'World'  2  3.0

DataFrameは、2次元のNumPy ndarrayとまったく同じように動作することを意図していません。

辞書のリストから#

In [53]: data2 = [{"a": 1, "b": 2}, {"a": 5, "b": 10, "c": 20}]

In [54]: pd.DataFrame(data2)
Out[54]: 
   a   b     c
0  1   2   NaN
1  5  10  20.0

In [55]: pd.DataFrame(data2, index=["first", "second"])
Out[55]: 
        a   b     c
first   1   2   NaN
second  5  10  20.0

In [56]: pd.DataFrame(data2, columns=["a", "b"])
Out[56]: 
   a   b
0  1   2
1  5  10

タプルの辞書から#

タプルの辞書を渡すことで、MultiIndexedフレームを自動的に作成できます。

In [57]: pd.DataFrame(
   ....:     {
   ....:         ("a", "b"): {("A", "B"): 1, ("A", "C"): 2},
   ....:         ("a", "a"): {("A", "C"): 3, ("A", "B"): 4},
   ....:         ("a", "c"): {("A", "B"): 5, ("A", "C"): 6},
   ....:         ("b", "a"): {("A", "C"): 7, ("A", "B"): 8},
   ....:         ("b", "b"): {("A", "D"): 9, ("A", "B"): 10},
   ....:     }
   ....: )
   ....: 
Out[57]: 
       a              b      
       b    a    c    a     b
A B  1.0  4.0  5.0  8.0  10.0
  C  2.0  3.0  6.0  7.0   NaN
  D  NaN  NaN  NaN  NaN   9.0

Seriesから#

結果は、入力Seriesと同じインデックスを持ち、列名が指定されていない場合に、元のSeriesの名前を列名とする1つの列を持つDataFrameになります。

In [58]: ser = pd.Series(range(3), index=list("abc"), name="ser")

In [59]: pd.DataFrame(ser)
Out[59]: 
   ser
a    0
b    1
c    2

namedtupleのリストから#

リスト内の最初のnamedtupleのフィールド名によって、DataFrameの列が決まります。残りのnamedtuple(またはタプル)は単にアンパックされ、その値がDataFrameの行に供給されます。これらのタプルのいずれかが最初のnamedtupleよりも短い場合、対応する行の後続の列は欠損値としてマークされます。いずれかが最初のnamedtupleよりも長い場合、ValueErrorが発生します。

In [60]: from collections import namedtuple

In [61]: Point = namedtuple("Point", "x y")

In [62]: pd.DataFrame([Point(0, 0), Point(0, 3), (2, 3)])
Out[62]: 
   x  y
0  0  0
1  0  3
2  2  3

In [63]: Point3D = namedtuple("Point3D", "x y z")

In [64]: pd.DataFrame([Point3D(0, 0, 0), Point3D(0, 3, 5), Point(2, 3)])
Out[64]: 
   x  y    z
0  0  0  0.0
1  0  3  5.0
2  2  3  NaN

dataclassesのリストから#

PEP557で導入されたData Classesは、DataFrameコンストラクターに渡すことができます。dataclassesのリストを渡すことは、辞書のリストを渡すことと同等です。

リスト内のすべての値はdataclassesである必要があり、リスト内の型を混在させるとTypeErrorが発生することに注意してください。

In [65]: from dataclasses import make_dataclass

In [66]: Point = make_dataclass("Point", [("x", int), ("y", int)])

In [67]: pd.DataFrame([Point(0, 0), Point(0, 3), Point(2, 3)])
Out[67]: 
   x  y
0  0  0
1  0  3
2  2  3

欠損データ

欠損データを含むDataFrameを構築するには、欠損値を表すためにnp.nanを使用します。または、numpy.MaskedArrayをDataFrameコンストラクターへのデータ引数として渡し、そのマスクされたエントリが欠損と見なされます。詳細については、欠損データを参照してください。

代替コンストラクター#

DataFrame.from_dict

DataFrame.from_dict()は、辞書の辞書または配列のようなシーケンスの辞書を受け取り、DataFrameを返します。これは、デフォルトで'columns'であるorientパラメーターを除いて、DataFrameコンストラクターのように動作します。ただし、辞書のキーを行ラベルとして使用するために'index'に設定できます。

In [68]: pd.DataFrame.from_dict(dict([("A", [1, 2, 3]), ("B", [4, 5, 6])]))
Out[68]: 
   A  B
0  1  4
1  2  5
2  3  6

orient='index'を渡すと、キーは行ラベルになります。この場合、必要な列名を渡すこともできます。

In [69]: pd.DataFrame.from_dict(
   ....:     dict([("A", [1, 2, 3]), ("B", [4, 5, 6])]),
   ....:     orient="index",
   ....:     columns=["one", "two", "three"],
   ....: )
   ....: 
Out[69]: 
   one  two  three
A    1    2      3
B    4    5      6

DataFrame.from_records

DataFrame.from_records()は、タプルのリストまたは構造化dtypeのndarrayを受け取ります。これは、通常のDataFrameコンストラクターと同様に動作します。ただし、結果のDataFrameインデックスは構造化dtypeの特定のフィールドになる可能性があります。

In [70]: data
Out[70]: 
array([(1, 2., b'Hello'), (2, 3., b'World')],
      dtype=[('A', '<i4'), ('B', '<f4'), ('C', 'S10')])

In [71]: pd.DataFrame.from_records(data, index="C")
Out[71]: 
          A    B
C               
b'Hello'  1  2.0
b'World'  2  3.0

列の選択、追加、削除#

DataFrameは、同じインデックスを持つSeriesオブジェクトの辞書のように意味的に扱うことができます。列の取得、設定、削除は、対応する辞書操作と同じ構文で動作します。

In [72]: df["one"]
Out[72]: 
a    1.0
b    2.0
c    3.0
d    NaN
Name: one, dtype: float64

In [73]: df["three"] = df["one"] * df["two"]

In [74]: df["flag"] = df["one"] > 2

In [75]: df
Out[75]: 
   one  two  three   flag
a  1.0  1.0    1.0  False
b  2.0  2.0    4.0  False
c  3.0  3.0    9.0   True
d  NaN  4.0    NaN  False

列は、辞書のように削除またはポップできます。

In [76]: del df["two"]

In [77]: three = df.pop("three")

In [78]: df
Out[78]: 
   one   flag
a  1.0  False
b  2.0  False
c  3.0   True
d  NaN  False

スカラー値を挿入すると、当然、列を埋めるために伝播されます。

In [79]: df["foo"] = "bar"

In [80]: df
Out[80]: 
   one   flag  foo
a  1.0  False  bar
b  2.0  False  bar
c  3.0   True  bar
d  NaN  False  bar

DataFrameと同じインデックスを持たないSeriesを挿入すると、DataFrameのインデックスに適合されます。

In [81]: df["one_trunc"] = df["one"][:2]

In [82]: df
Out[82]: 
   one   flag  foo  one_trunc
a  1.0  False  bar        1.0
b  2.0  False  bar        2.0
c  3.0   True  bar        NaN
d  NaN  False  bar        NaN

生のndarrayを挿入できますが、その長さはDataFrameのインデックスの長さと一致する必要があります。

デフォルトでは、列は最後に挿入されます。DataFrame.insert()は、列の特定の位置に挿入します。

In [83]: df.insert(1, "bar", df["one"])

In [84]: df
Out[84]: 
   one  bar   flag  foo  one_trunc
a  1.0  1.0  False  bar        1.0
b  2.0  2.0  False  bar        2.0
c  3.0  3.0   True  bar        NaN
d  NaN  NaN  False  bar        NaN

メソッドチェーンでの新しい列の割り当て#

dplyrmutate動詞に触発されて、DataFrameには、既存の列から派生する可能性のある新しい列を簡単に作成できるassign()メソッドがあります。

In [85]: iris = pd.read_csv("data/iris.data")

In [86]: iris.head()
Out[86]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name
0          5.1         3.5          1.4         0.2  Iris-setosa
1          4.9         3.0          1.4         0.2  Iris-setosa
2          4.7         3.2          1.3         0.2  Iris-setosa
3          4.6         3.1          1.5         0.2  Iris-setosa
4          5.0         3.6          1.4         0.2  Iris-setosa

In [87]: iris.assign(sepal_ratio=iris["SepalWidth"] / iris["SepalLength"]).head()
Out[87]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name  sepal_ratio
0          5.1         3.5          1.4         0.2  Iris-setosa     0.686275
1          4.9         3.0          1.4         0.2  Iris-setosa     0.612245
2          4.7         3.2          1.3         0.2  Iris-setosa     0.680851
3          4.6         3.1          1.5         0.2  Iris-setosa     0.673913
4          5.0         3.6          1.4         0.2  Iris-setosa     0.720000

上記の例では、事前に計算された値を挿入しました。また、代入される DataFrame で評価される 1 つの引数を持つ関数を渡すこともできます。

In [88]: iris.assign(sepal_ratio=lambda x: (x["SepalWidth"] / x["SepalLength"])).head()
Out[88]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name  sepal_ratio
0          5.1         3.5          1.4         0.2  Iris-setosa     0.686275
1          4.9         3.0          1.4         0.2  Iris-setosa     0.612245
2          4.7         3.2          1.3         0.2  Iris-setosa     0.680851
3          4.6         3.1          1.5         0.2  Iris-setosa     0.673913
4          5.0         3.6          1.4         0.2  Iris-setosa     0.720000

assign() は、常にデータのコピーを返し、元の DataFrame は変更しません。

挿入する実際の値の代わりに、呼び出し可能オブジェクトを渡すことは、手元に DataFrame への参照がない場合に便利です。これは、一連の操作で assign() を使用する場合によくあります。たとえば、Sepal Length が 5 より大きい観測値のみに DataFrame を制限し、比率を計算してプロットすることができます。

In [89]: (
   ....:     iris.query("SepalLength > 5")
   ....:     .assign(
   ....:         SepalRatio=lambda x: x.SepalWidth / x.SepalLength,
   ....:         PetalRatio=lambda x: x.PetalWidth / x.PetalLength,
   ....:     )
   ....:     .plot(kind="scatter", x="SepalRatio", y="PetalRatio")
   ....: )
   ....: 
Out[89]: <Axes: xlabel='SepalRatio', ylabel='PetalRatio'>
../_images/basics_assign.png

関数が渡されるため、関数は代入される DataFrame で計算されます。重要なことは、これは sepal length が 5 より大きい行にフィルタリングされた DataFrame であるということです。フィルタリングが最初に行われ、次に比率の計算が行われます。これは、フィルタリングされた DataFrame への参照が利用できなかった例です。

assign() の関数シグネチャは、単に **kwargs です。キーは新しいフィールドの列名で、値は挿入する値(たとえば、Series または NumPy 配列)か、DataFrame で呼び出される 1 つの引数の関数です。新しい値が挿入された元の DataFrameコピーが返されます。

**kwargs の順序は保持されます。これにより、**kwargs の後の式が、同じ assign() で前に作成された列を参照できる、依存割り当てが可能になります。

In [90]: dfa = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})

In [91]: dfa.assign(C=lambda x: x["A"] + x["B"], D=lambda x: x["A"] + x["C"])
Out[91]: 
   A  B  C   D
0  1  4  5   6
1  2  5  7   9
2  3  6  9  12

2 番目の式では、x['C'] は、dfa['A'] + dfa['B'] と等しい、新しく作成された列を参照します。

インデックス/選択#

インデックス作成の基本は次のとおりです。

操作

構文

結果

列の選択

df[col]

Series

ラベルによる行の選択

df.loc[label]

Series

整数位置による行の選択

df.iloc[loc]

Series

行のスライス

df[5:10]

DataFrame

ブールベクトルによる行の選択

df[bool_vec]

DataFrame

たとえば、行の選択は、インデックスが DataFrame の列である Series を返します。

In [92]: df.loc["b"]
Out[92]: 
one            2.0
bar            2.0
flag         False
foo            bar
one_trunc      2.0
Name: b, dtype: object

In [93]: df.iloc[2]
Out[93]: 
one           3.0
bar           3.0
flag         True
foo           bar
one_trunc     NaN
Name: c, dtype: object

高度なラベルベースのインデックス作成とスライスの詳細については、インデックス作成に関するセクションを参照してください。ラベルの新しいセットへの再インデックス作成/適合の基本については、再インデックス作成に関するセクションで説明します。

データ整列と算術演算#

DataFrame オブジェクト間のデータ整列は、列とインデックス (行ラベル) の両方で自動的に整列します。繰り返しますが、結果のオブジェクトには、列と行のラベルの和集合が含まれます。

In [94]: df = pd.DataFrame(np.random.randn(10, 4), columns=["A", "B", "C", "D"])

In [95]: df2 = pd.DataFrame(np.random.randn(7, 3), columns=["A", "B", "C"])

In [96]: df + df2
Out[96]: 
          A         B         C   D
0  0.045691 -0.014138  1.380871 NaN
1 -0.955398 -1.501007  0.037181 NaN
2 -0.662690  1.534833 -0.859691 NaN
3 -2.452949  1.237274 -0.133712 NaN
4  1.414490  1.951676 -2.320422 NaN
5 -0.494922 -1.649727 -1.084601 NaN
6 -1.047551 -0.748572 -0.805479 NaN
7       NaN       NaN       NaN NaN
8       NaN       NaN       NaN NaN
9       NaN       NaN       NaN NaN

DataFrameSeries の間で操作を行う場合、デフォルトの動作は、SeriesインデックスDataFrameに合わせるため、行方向にブロードキャストします。たとえば

In [97]: df - df.iloc[0]
Out[97]: 
          A         B         C         D
0  0.000000  0.000000  0.000000  0.000000
1 -1.359261 -0.248717 -0.453372 -1.754659
2  0.253128  0.829678  0.010026 -1.991234
3 -1.311128  0.054325 -1.724913 -1.620544
4  0.573025  1.500742 -0.676070  1.367331
5 -1.741248  0.781993 -1.241620 -2.053136
6 -1.240774 -0.869551 -0.153282  0.000430
7 -0.743894  0.411013 -0.929563 -0.282386
8 -1.194921  1.320690  0.238224 -1.482644
9  2.293786  1.856228  0.773289 -1.446531

マッチングとブロードキャストの動作を明示的に制御するには、柔軟なバイナリ演算に関するセクションを参照してください。

スカラーによる算術演算は、要素ごとに行われます。

In [98]: df * 5 + 2
Out[98]: 
           A         B         C          D
0   3.359299 -0.124862  4.835102   3.381160
1  -3.437003 -1.368449  2.568242  -5.392133
2   4.624938  4.023526  4.885230  -6.575010
3  -3.196342  0.146766 -3.789461  -4.721559
4   6.224426  7.378849  1.454750  10.217815
5  -5.346940  3.785103 -1.373001  -6.884519
6  -2.844569 -4.472618  4.068691   3.383309
7  -0.360173  1.930201  0.187285   1.969232
8  -2.615303  6.478587  6.026220  -4.032059
9  14.828230  9.156280  8.701544  -3.851494

In [99]: 1 / df
Out[99]: 
          A          B         C           D
0  3.678365  -2.353094  1.763605    3.620145
1 -0.919624  -1.484363  8.799067   -0.676395
2  1.904807   2.470934  1.732964   -0.583090
3 -0.962215  -2.697986 -0.863638   -0.743875
4  1.183593   0.929567 -9.170108    0.608434
5 -0.680555   2.800959 -1.482360   -0.562777
6 -1.032084  -0.772485  2.416988    3.614523
7 -2.118489 -71.634509 -2.758294 -162.507295
8 -1.083352   1.116424  1.241860   -0.828904
9  0.389765   0.698687  0.746097   -0.854483

In [100]: df ** 4
Out[100]: 
           A             B         C             D
0   0.005462  3.261689e-02  0.103370  5.822320e-03
1   1.398165  2.059869e-01  0.000167  4.777482e+00
2   0.075962  2.682596e-02  0.110877  8.650845e+00
3   1.166571  1.887302e-02  1.797515  3.265879e+00
4   0.509555  1.339298e+00  0.000141  7.297019e+00
5   4.661717  1.624699e-02  0.207103  9.969092e+00
6   0.881334  2.808277e+00  0.029302  5.858632e-03
7   0.049647  3.797614e-08  0.017276  1.433866e-09
8   0.725974  6.437005e-01  0.420446  2.118275e+00
9  43.329821  4.196326e+00  3.227153  1.875802e+00

ブール演算子も、要素ごとに行われます。

In [101]: df1 = pd.DataFrame({"a": [1, 0, 1], "b": [0, 1, 1]}, dtype=bool)

In [102]: df2 = pd.DataFrame({"a": [0, 1, 1], "b": [1, 1, 0]}, dtype=bool)

In [103]: df1 & df2
Out[103]: 
       a      b
0  False  False
1  False   True
2   True  False

In [104]: df1 | df2
Out[104]: 
      a     b
0  True  True
1  True  True
2  True  True

In [105]: df1 ^ df2
Out[105]: 
       a      b
0   True   True
1   True  False
2  False   True

In [106]: -df1
Out[106]: 
       a      b
0  False   True
1   True  False
2  False  False

転置#

転置するには、ndarray と同様に、T 属性または DataFrame.transpose() にアクセスします。

# only show the first 5 rows
In [107]: df[:5].T
Out[107]: 
          0         1         2         3         4
A  0.271860 -1.087401  0.524988 -1.039268  0.844885
B -0.424972 -0.673690  0.404705 -0.370647  1.075770
C  0.567020  0.113648  0.577046 -1.157892 -0.109050
D  0.276232 -1.478427 -1.715002 -1.344312  1.643563

NumPy 関数との DataFrame の相互運用性#

ほとんどの NumPy 関数は、Series および DataFrame で直接呼び出すことができます。

In [108]: np.exp(df)
Out[108]: 
           A         B         C         D
0   1.312403  0.653788  1.763006  1.318154
1   0.337092  0.509824  1.120358  0.227996
2   1.690438  1.498861  1.780770  0.179963
3   0.353713  0.690288  0.314148  0.260719
4   2.327710  2.932249  0.896686  5.173571
5   0.230066  1.429065  0.509360  0.169161
6   0.379495  0.274028  1.512461  1.318720
7   0.623732  0.986137  0.695904  0.993865
8   0.397301  2.449092  2.237242  0.299269
9  13.009059  4.183951  3.820223  0.310274

In [109]: np.asarray(df)
Out[109]: 
array([[ 0.2719, -0.425 ,  0.567 ,  0.2762],
       [-1.0874, -0.6737,  0.1136, -1.4784],
       [ 0.525 ,  0.4047,  0.577 , -1.715 ],
       [-1.0393, -0.3706, -1.1579, -1.3443],
       [ 0.8449,  1.0758, -0.109 ,  1.6436],
       [-1.4694,  0.357 , -0.6746, -1.7769],
       [-0.9689, -1.2945,  0.4137,  0.2767],
       [-0.472 , -0.014 , -0.3625, -0.0062],
       [-0.9231,  0.8957,  0.8052, -1.2064],
       [ 2.5656,  1.4313,  1.3403, -1.1703]])

DataFrame は、そのインデックス作成のセマンティクスとデータモデルが n 次元配列とは異なる場所があるため、ndarray の直接の代替として意図されていません。

Series は、NumPy のユニバーサル関数で動作できるようにする __array_ufunc__ を実装します。

ufunc は、Series の基になる配列に適用されます。

In [110]: ser = pd.Series([1, 2, 3, 4])

In [111]: np.exp(ser)
Out[111]: 
0     2.718282
1     7.389056
2    20.085537
3    54.598150
dtype: float64

複数の Series が ufunc に渡されると、操作を実行する前に整列されます。

ライブラリの他の部分と同様に、pandas は、複数の入力を持つ ufunc の一部として、ラベル付きの入力を自動的に整列します。たとえば、異なる順序のラベルを持つ 2 つの Seriesnumpy.remainder() を使用すると、操作の前に整列されます。

In [112]: ser1 = pd.Series([1, 2, 3], index=["a", "b", "c"])

In [113]: ser2 = pd.Series([1, 3, 5], index=["b", "a", "c"])

In [114]: ser1
Out[114]: 
a    1
b    2
c    3
dtype: int64

In [115]: ser2
Out[115]: 
b    1
a    3
c    5
dtype: int64

In [116]: np.remainder(ser1, ser2)
Out[116]: 
a    1
b    0
c    3
dtype: int64

通常どおり、2 つのインデックスの和集合が取得され、重複しない値は欠損値で埋められます。

In [117]: ser3 = pd.Series([2, 4, 6], index=["b", "c", "d"])

In [118]: ser3
Out[118]: 
b    2
c    4
d    6
dtype: int64

In [119]: np.remainder(ser1, ser3)
Out[119]: 
a    NaN
b    0.0
c    3.0
d    NaN
dtype: float64

バイナリ ufunc が SeriesIndex に適用されると、Series の実装が優先され、Series が返されます。

In [120]: ser = pd.Series([1, 2, 3])

In [121]: idx = pd.Index([4, 5, 6])

In [122]: np.maximum(ser, idx)
Out[122]: 
0    4
1    5
2    6
dtype: int64

NumPy ufunc は、たとえば arrays.SparseArrayスパース計算を参照)など、ndarray 配列ではない配列によってバックアップされた Series に安全に適用できます。可能な場合、ufunc は、基になるデータを ndarray に変換せずに適用されます。

コンソール表示#

非常に大きな DataFrame は、コンソールに表示するために切り捨てられます。info() を使用して概要を取得することもできます。(baseball データセットは、plyr R パッケージのものです)

In [123]: baseball = pd.read_csv("data/baseball.csv")

In [124]: print(baseball)
       id     player  year  stint team  lg  ...    so  ibb  hbp   sh   sf  gidp
0   88641  womacto01  2006      2  CHN  NL  ...   4.0  0.0  0.0  3.0  0.0   0.0
1   88643  schilcu01  2006      1  BOS  AL  ...   1.0  0.0  0.0  0.0  0.0   0.0
..    ...        ...   ...    ...  ...  ..  ...   ...  ...  ...  ...  ...   ...
98  89533   aloumo01  2007      1  NYN  NL  ...  30.0  5.0  2.0  0.0  3.0  13.0
99  89534  alomasa02  2007      1  NYN  NL  ...   3.0  0.0  0.0  0.0  0.0   0.0

[100 rows x 23 columns]

In [125]: baseball.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100 entries, 0 to 99
Data columns (total 23 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   id      100 non-null    int64  
 1   player  100 non-null    object 
 2   year    100 non-null    int64  
 3   stint   100 non-null    int64  
 4   team    100 non-null    object 
 5   lg      100 non-null    object 
 6   g       100 non-null    int64  
 7   ab      100 non-null    int64  
 8   r       100 non-null    int64  
 9   h       100 non-null    int64  
 10  X2b     100 non-null    int64  
 11  X3b     100 non-null    int64  
 12  hr      100 non-null    int64  
 13  rbi     100 non-null    float64
 14  sb      100 non-null    float64
 15  cs      100 non-null    float64
 16  bb      100 non-null    int64  
 17  so      100 non-null    float64
 18  ibb     100 non-null    float64
 19  hbp     100 non-null    float64
 20  sh      100 non-null    float64
 21  sf      100 non-null    float64
 22  gidp    100 non-null    float64
dtypes: float64(9), int64(11), object(3)
memory usage: 18.1+ KB

ただし、DataFrame.to_string() を使用すると、DataFrame の表形式の文字列表現が返されますが、常にコンソールの幅に収まるとは限りません。

In [126]: print(baseball.iloc[-20:, :12].to_string())
       id     player  year  stint team  lg    g   ab   r    h  X2b  X3b
80  89474  finlest01  2007      1  COL  NL   43   94   9   17    3    0
81  89480  embreal01  2007      1  OAK  AL    4    0   0    0    0    0
82  89481  edmonji01  2007      1  SLN  NL  117  365  39   92   15    2
83  89482  easleda01  2007      1  NYN  NL   76  193  24   54    6    0
84  89489  delgaca01  2007      1  NYN  NL  139  538  71  139   30    0
85  89493  cormirh01  2007      1  CIN  NL    6    0   0    0    0    0
86  89494  coninje01  2007      2  NYN  NL   21   41   2    8    2    0
87  89495  coninje01  2007      1  CIN  NL   80  215  23   57   11    1
88  89497  clemero02  2007      1  NYA  AL    2    2   0    1    0    0
89  89498  claytro01  2007      2  BOS  AL    8    6   1    0    0    0
90  89499  claytro01  2007      1  TOR  AL   69  189  23   48   14    0
91  89501  cirilje01  2007      2  ARI  NL   28   40   6    8    4    0
92  89502  cirilje01  2007      1  MIN  AL   50  153  18   40    9    2
93  89521  bondsba01  2007      1  SFN  NL  126  340  75   94   14    0
94  89523  biggicr01  2007      1  HOU  NL  141  517  68  130   31    3
95  89525  benitar01  2007      2  FLO  NL   34    0   0    0    0    0
96  89526  benitar01  2007      1  SFN  NL   19    0   0    0    0    0
97  89530  ausmubr01  2007      1  HOU  NL  117  349  38   82   16    3
98  89533   aloumo01  2007      1  NYN  NL   87  328  51  112   19    1
99  89534  alomasa02  2007      1  NYN  NL    8   22   1    3    1    0

幅の広い DataFrame は、デフォルトで複数行にわたって印刷されます。

In [127]: pd.DataFrame(np.random.randn(3, 12))
Out[127]: 
         0         1         2   ...        9         10        11
0 -1.226825  0.769804 -1.281247  ... -1.110336 -0.619976  0.149748
1 -0.732339  0.687738  0.176444  ...  1.462696 -1.743161 -0.826591
2 -0.345352  1.314232  0.690579  ...  0.896171 -0.487602 -0.082240

[3 rows x 12 columns]

display.width オプションを設定して、1 行に印刷する量を変更できます。

In [128]: pd.set_option("display.width", 40)  # default is 80

In [129]: pd.DataFrame(np.random.randn(3, 12))
Out[129]: 
         0         1         2   ...        9         10        11
0 -2.182937  0.380396  0.084844  ... -0.023688  2.410179  1.450520
1  0.206053 -0.251905 -2.213588  ... -0.025747 -0.988387  0.094055
2  1.262731  1.289997  0.082423  ... -0.281461  0.030711  0.109121

[3 rows x 12 columns]

display.max_colwidth を設定することで、個々の列の最大幅を調整できます。

In [130]: datafile = {
   .....:     "filename": ["filename_01", "filename_02"],
   .....:     "path": [
   .....:         "media/user_name/storage/folder_01/filename_01",
   .....:         "media/user_name/storage/folder_02/filename_02",
   .....:     ],
   .....: }
   .....: 

In [131]: pd.set_option("display.max_colwidth", 30)

In [132]: pd.DataFrame(datafile)
Out[132]: 
      filename                           path
0  filename_01  media/user_name/storage/fo...
1  filename_02  media/user_name/storage/fo...

In [133]: pd.set_option("display.max_colwidth", 100)

In [134]: pd.DataFrame(datafile)
Out[134]: 
      filename                                           path
0  filename_01  media/user_name/storage/folder_01/filename_01
1  filename_02  media/user_name/storage/folder_02/filename_02

expand_frame_repr オプションを介して、この機能を無効にすることもできます。これにより、テーブルが 1 つのブロックで印刷されます。

DataFrame 列の属性アクセスと IPython の補完#

DataFrame の列ラベルが有効な Python 変数名である場合、属性のように列にアクセスできます。

In [135]: df = pd.DataFrame({"foo1": np.random.randn(5), "foo2": np.random.randn(5)})

In [136]: df
Out[136]: 
       foo1      foo2
0  1.126203  0.781836
1 -0.977349 -1.071357
2  1.474071  0.441153
3 -0.064034  2.353925
4 -1.282782  0.583787

In [137]: df.foo1
Out[137]: 
0    1.126203
1   -0.977349
2    1.474071
3   -0.064034
4   -1.282782
Name: foo1, dtype: float64

列は、IPython 補完メカニズムにも接続されているため、タブ補完できます。

In [5]: df.foo<TAB>  # noqa: E225, E999
df.foo1  df.foo2