データフレーム#
データフレーム(DataFrame)はPolarsの中心的なデータ構造で、行と列からなる2次元のデータを表現します。各列は同じデータ型を持ち、列ごとに型が異なることが可能です。
import polars as pl
from helper.jupyter import row
データフレームの作成#
本節では、Pythonの他のオブジェクトからデータフレームオブジェクトを作成する方法について説明します。
リストと辞書の組み合わせ#
次のプログラムは、異なるPythonのデータ構造(辞書のリスト、リストの辞書、リストのリスト)を使用してDataFrame
を作成します。
辞書のリスト(
list[dict]
): 辞書のキーが列名となり、リスト内の各辞書が1行分のデータとなります。リストの辞書(
dict[list]
): 辞書のキーが列名となり、各リストの要素がその列に対応するデータになります。リストのリスト(
list[list]
):schema
引数で列名を指定します。orient
引数は'row'
の場合は、データの方向が行単位であることを指定して、内部の一つリストが1行分のデータとなります。orient
引数は'col'
の場合は、データの方向が列単位であることを指定して、内部の一つリストが1列分のデータとなります。この例では内部リストのデータ型一致しないので、strict=False
で自動型変換を有効にします。
# list[dict]
dict_in_list = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Charlie", "age": 35}
]
df1 = pl.DataFrame(dict_in_list)
# dict[list]
list_in_dict = {
"name": ["Alice", "Bob", "Charlie"],
"age": [30, 25, 35],
}
df2 = pl.DataFrame(list_in_dict)
# list[list]
list_in_list = [
["Alice", 30],
["Bob", 25],
["Charlie", 35]
]
columns = ["name", "age"] # カラム名を指定
df3 = pl.DataFrame(list_in_list, schema=columns, orient='row')
df4 = pl.DataFrame(list_in_list, schema=['p1', 'p2', 'p3'], orient='col', strict=False)
row(df1, df2, df3, df4)
shape: (3, 2)
|
shape: (3, 2)
|
shape: (3, 2)
|
shape: (2, 3)
|
次のdata
は辞書形式で、次のようなデータを持っています:
"point"
キーの値は辞書のリスト (list[dict]
) で、各辞書にはx
とy
の2つのキーが含まれています。"weight"
キーの値は整数のリスト (list[int]
) です。
データフレームに変換するとき、外側の辞書のキーは列名になり、point
列の要素はStruct
型(構造体)に変換されます。
data = {
"point": [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 5, "y": 6}],
"weight": [5, 4, 8],
}
pl.DataFrame(data)
point | weight |
---|---|
struct[2] | i64 |
{1,2} | 5 |
{3,4} | 4 |
{5,6} | 8 |
NumPyの配列#
NumPyの配列を扱う際、以下のようにlistとNumPy配列と互換性を持ちます。
dict[list]
とdict[1次元配列]
は同じ扱いlist[list]
と2次元配列は同じ扱い
import numpy as np
array_in_dict = {
"x": np.array([1, 3, 5]),
"y": np.array([2, 4, 6]),
}
df1 = pl.DataFrame(array_in_dict)
array_2d = np.array([[1, 2], [3, 4], [5, 6]])
df2 = pl.DataFrame(array_2d, schema=['x', 'y'], orient='row')
df3 = pl.DataFrame(array_2d, schema=['p1', 'p2', 'p3'], orient='col')
row(df1, df2, df3)
shape: (3, 2)
|
shape: (3, 2)
|
shape: (2, 3)
|
1次元の構造化配列をデータフレームに変換する場合は、配列の各フィールドはデータフレームの各列になります。
arr = np.array([
(1, 30),
(2, 25),
(3, 35)], dtype=[('x', 'i2'), ('y', 'i2')])
pl.DataFrame(arr)
x | y |
---|---|
i16 | i16 |
1 | 30 |
2 | 25 |
3 | 35 |
Seriesを含むデータ#
pl.Series
を扱う場合、list[Series]
や dict[Series]
の形式をデータフレームに変換することがよくあります。どちらの場合も、それぞれの Series
はデータフレームの列になりますが、列名の扱いが異なります。
list[Series]
: 列名はSeries
の名前がそのまま使われます。dict[Series]
: 列名は辞書のキーが使われます。
sx = pl.Series('x', [1, 2, 3])
sy = pl.Series('y', [4, 5, 6])
df1 = pl.DataFrame([sx, sy])
df2 = pl.DataFrame({'A':sx, 'B':sy})
row(df1, df2)
shape: (3, 2)
|
shape: (3, 2)
|
pl.from_*()関数#
from_
で始まる関数は、さまざまなデータ型をデータフレームに変換するために使用されます。これらの関数を利用すると、意図しないデータ変換が発生しにくく、コードのロバスト性を向上させることができます。
pl.from_dict()
:dict[list]
のデータから変換pl.from_dicts()
:list[dict]
のデータから変換pl.from_numpy()
: NumPy の配列から変換pl.from_records()
:list[list]
のデータから変換pl.from_pandas()
: PandasのDataFrame
オブジェクトから変換pl.from_arrow()
: pyarrowのArray
或いはTable
オブジェクトから変換
データフレームの属性#
df = pl.DataFrame(
{
"a": [3, 3, 3, 4],
"b": [4.0, 12, 6, 7],
"g": ['A', 'B', 'A', 'B']
}
)
shape
属性でデータフレームの形状(高さ、幅)を取得できます。又height
とwidth
属性で高さと幅を取得することもできます。len()
関数でも高さを取得できます。
print(f"{df.shape = }")
print(f"{df.height = }")
print(f"{df.width = }")
print(f"{len(df) = }")
df.shape = (4, 3)
df.height = 4
df.width = 3
len(df) = 4
columns
属性で列名のリストを取得できます。
df.columns
['a', 'b', 'g']
schema
属性で列名と列のデータ型を保存する辞書オブジェクトを取得できます。
df.schema
Schema([('a', Int64), ('b', Float64), ('g', String)])
df.schema['g']
String
dtypes
属性で、各個列のデータ型を保存するリストを取得できます。
df.dtypes
[Int64, Float64, String]
flags
属性で各個列のソート状態を取得できます。
df.flags
{'a': {'SORTED_ASC': False, 'SORTED_DESC': False},
'b': {'SORTED_ASC': False, 'SORTED_DESC': False},
'g': {'SORTED_ASC': False, 'SORTED_DESC': False}}
データフレームからデータ取得#
本節は、データフレームから列、行、或いは単一の値を取得する方法について説明します。
列を取得#
PolarsでDataFrameから列データをSeriesとして取得する方法はいくつかあります。
DataFrame.to_series()
: インデックスで列を取得します。DataFrame.get_column()
: 列名で列を取得します。DataFrame.get_columns()
: すべての列を取得します。DataFrame.iter_columns()
: 列のイテレーターを取得します。
DataFrame.to_series()
メソッドを使用すると、指定したインデックスに基づいて列を Series として取得できます。DataFrame.get_column()
メソッドを使用すると、列名を指定して Series を取得できます。DataFrame["column_name"]
のような辞書形式で列名を指定して Seriesを取得することもできます。
s1 = df.to_series(0)
s2 = df.get_column('b')
s3 = df['g']
row(s1, s2, s3)
shape: (4,)
|
shape: (4,)
|
shape: (4,)
|
DataFrame.get_columns()
メソッドは、DataFrame 内のすべての列を Series のリストとして取得します。
sa, sb, sg = df.get_columns()
row(sa, sb, sg)
shape: (4,)
|
shape: (4,)
|
shape: (4,)
|
DataFrame.iter_columns()
は、DataFrame内のすべての列を一つずつ返します。
for col in df.iter_columns():
print(col.name, col.to_list())
a [3, 3, 3, 4]
b [4.0, 12.0, 6.0, 7.0]
g ['A', 'B', 'A', 'B']
Seriesオブジェクト#
print(f"{s1.name = }")
print(f"{s1.dtype = }")
print(f"{s1.flags = }")
print(f"{s1.shape = }")
print(f"{len(s1) = }")
s1.name = 'a'
s1.dtype = Int64
s1.flags = {'SORTED_ASC': False, 'SORTED_DESC': False}
s1.shape = (4,)
len(s1) = 4
to_numpy()メソッド#
Series.to_numpy()
またはSeries.to_list()
メソッドを使用すると、Series
オブジェクトをNumPyの配列やリストに変換することができます。
print(f'{sa.to_numpy() = }')
print(f'{sa.to_list() = }')
sa.to_numpy() = array([3, 3, 3, 4], dtype=int64)
sa.to_list() = [3, 3, 3, 4]
DataFrame.to_numpy()
でNumPyの配列に変換することができます。デフォルトはすべての値を一番上位のデータ型に変換します。数値と文字列混在のデータの場合は、dtype=object
の配列になります。
df.to_numpy()
array([[3, 4.0, 'A'],
[3, 12.0, 'B'],
[3, 6.0, 'A'],
[4, 7.0, 'B']], dtype=object)
structured
引数をTrue
にすれば、構造化配列に変換します。
df.to_numpy(structured=True)
array([(3, 4., 'A'), (3, 12., 'B'), (3, 6., 'A'), (4, 7., 'B')],
dtype=[('a', '<i8'), ('b', '<f8'), ('g', '<U1')])
Series
オブジェクトの要素がリストの場合、to_numpy()
メソッドを使用すると、dtype=object
の配列が得られます。この配列の各要素は、さらに配列になっています。このような構造は、高速な処理には適していません。
s_list = pl.Series('s_list', [[1, 2, 3], [4, 5, 6], [7, 8, 9]])
s_list.to_numpy()
array([array([1, 2, 3], dtype=int64), array([4, 5, 6], dtype=int64),
array([7, 8, 9], dtype=int64)], dtype=object)
Series
オブジェクトの要素が Array
の場合、to_numpy()
を使用すると、多次元配列になります。
s_arr = pl.Series('s_arr', [[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=pl.Array(pl.Int8, 3))
s_arr.to_numpy()
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]], dtype=int8)
Series
オブジェクトの要素が Struct
の場合、to_numpy()
を使用すると 2 次元配列になり、各要素のデータ型は最も上位のデータ型に統一されます。次の例では、Int64
と Float64
が Float64
に統一されています。
s_struct = pl.Series('s_struct', [{"x": 1, "y": 2.0}, {"x": 3, "y": 4.0}, {"x": 5, "y": 6.0}])
s_struct.to_numpy()
array([[1., 2.],
[3., 4.],
[5., 6.]])
NumPyの構造体配列に変換するには、まず struct.unnest()
を使用して DataFrame
に変換し、その後 to_numpy(structured=True)
を使用します。
s_struct.struct.unnest().to_numpy(structured=True)
array([(1, 2.), (3, 4.), (5, 6.)], dtype=[('x', '<i8'), ('y', '<f8')])
to_dict()メソッド#
to_dict()
メソッドでdict[Series]
型の辞書に変換します。
df.to_dict()
{'a': shape: (4,)
Series: 'a' [i64]
[
3
3
3
4
],
'b': shape: (4,)
Series: 'b' [f64]
[
4.0
12.0
6.0
7.0
],
'g': shape: (4,)
Series: 'g' [str]
[
"A"
"B"
"A"
"B"
]}
to_dicts()メソッド#
to_dicts()
メソッドでlist[dict]
型のリストに変換します。
df.to_dicts()
[{'a': 3, 'b': 4.0, 'g': 'A'},
{'a': 3, 'b': 12.0, 'g': 'B'},
{'a': 3, 'b': 6.0, 'g': 'A'},
{'a': 4, 'b': 7.0, 'g': 'B'}]
to_arrow()メソッド#
to_arrow()
メソッドでpyarrowのTable
オブジェクトに変換します。このTable
オブジェクトはDataFrame
と同じデータメモリを使用するため、変換は高速です。
df.to_arrow()
pyarrow.Table
a: int64
b: double
g: large_string
----
a: [[3,3,3,4]]
b: [[4,12,6,7]]
g: [["A","B","A","B"]]
行を取得#
DataFrame.row(index)
: 特定のインデックスにある行をタプルとして取得します。DataFrame.rows()
: DataFrame のすべての行をタプルのリストとして取得します。DataFrame.iter_rows()
: DataFrame の各行に対してイテレーションを行い、各行をタプルとして返します。DataFrame.rows_by_key()
: 指定したキーに基づいて行をグループ化し、各グループに属する行をタプルのリストとして返します。
DataFrame.row(index)
は、指定したインデックス(行番号)に対応する行を取得するためのメソッドです。このメソッドは、特定の行をタプル形式で返します。
df.row(2)
(3, 6.0, 'A')
named
引数をTrue
にすることで、列名を含む辞書形式で返します。
df.row(2, named=True)
{'a': 3, 'b': 6.0, 'g': 'A'}
by_predicate
で演算式がTrueになる行を取得することができます。複数の行は条件に満たす場合は、エラーになります。
df.row(by_predicate=pl.col.a == pl.col.a.max())
(4, 7.0, 'B')
DataFrame.rows()
は、DataFrame 内のすべての行をタプルのリストとして取得するメソッドです。DataFrame 全体のデータを行単位で操作したい場合に便利です。又、DataFrame.iter_rows()
は各行に対してイテレーションを行うためのメソッドです。
df.rows()
[(3, 4.0, 'A'), (3, 12.0, 'B'), (3, 6.0, 'A'), (4, 7.0, 'B')]
df.rows(named=True)
[{'a': 3, 'b': 4.0, 'g': 'A'},
{'a': 3, 'b': 12.0, 'g': 'B'},
{'a': 3, 'b': 6.0, 'g': 'A'},
{'a': 4, 'b': 7.0, 'g': 'B'}]
for row_ in df.iter_rows():
print(row_)
(3, 4.0, 'A')
(3, 12.0, 'B')
(3, 6.0, 'A')
(4, 7.0, 'B')
DataFrame.rows_by_key()
は、指定したキー(列)に基づいて DataFrame 内の行をグループ化し、そのキーに対応する行をタプルのリストとして返すメソッドです。
df.rows_by_key('g')
defaultdict(list, {'A': [(3, 4.0), (3, 6.0)], 'B': [(3, 12.0), (4, 7.0)]})
df.rows_by_key('g', named=True)
defaultdict(list,
{'A': [{'a': 3, 'b': 4.0}, {'a': 3, 'b': 6.0}],
'B': [{'a': 3, 'b': 12.0}, {'a': 4, 'b': 7.0}]})
単一の値#
DataFrame.item()
は、DataFrameから単一の値を取得するためのメソッドです。
df.item(2, 'a')
3
DataFrameに値が一つだけ含まれる場合、引数を省略できます。列の統計値を取得して、それを別の計算に利用する際によく使用されます。
df.select(pl.col('a').mean()).item() / 100
0.0325
[]
によるインデックス操作#
PolarsのDataFrameは、NumPyやPandasのように[]
を使用したインデックス操作をサポートしています。
Warning
Polarsでは[]
を使ったインデックス操作が可能ですが、推奨される方法ではありません。filter()
やselect()
などのメソッドを使って演算式と組み合わせた操作を行う方が推奨されます。
以下の例では、サンプルデータを使用してインデックス操作を説明します。
df = pl.DataFrame({
"a": [1, 2, 3, 4],
"b": [5, 6, 7, 8],
"c": ["x", "y", "z", "w"],
"d": ["a", "b", "c", "d"]
})
要素の取得#
インデックスが1つの場合
インデックスが1つだけ指定される場合、その型によって挙動が異なります。
整数、整数のリスト、整数のスライスの場合: 行を選択します。スライスの場合、終了値は含まれません。
row(df[0], df[[0, 2]], df[1:3])
shape: (1, 4)
|
shape: (2, 4)
|
shape: (2, 4)
|
文字列、文字列のリスト、文字列のスライスの場合: 列を選択します。スライスの場合、終了値が含まれます。
row(df["a"], df[["a", "d"]], df["a":"c"])
shape: (4,)
|
shape: (4, 2)
|
shape: (4, 3)
|
インデックスが2つの場合
インデックスが2つ指定される場合、1つ目は行の選択、2つ目は列の選択に使われます。
行のインデックスには整数、整数のリスト、またはスライスを使用します。
列のインデックスには整数、文字列、整数のリスト、文字列のリスト、またはスライスを使用できます。
両方が単一の値の場合: 特定の要素を抽出します。結果はPythonの基本データ型となります。
print(f"{df[0, 'b'] = }") # 行0、列"b"の値を抽出
print(f"{df[0, 1] = }") # 行0、列インデックス1の値を抽出
df[0, 'b'] = 5
df[0, 1] = 5
列のインデックスがリストやスライスの場合: 結果は
DataFrame
オブジェクトになります。
row(df[0, ["a", "c"]], df[1, "a":"c"], df[2, [0, 1, 3]], df[3, ::2])
shape: (1, 2)
|
shape: (1, 3)
|
shape: (1, 3)
|
shape: (1, 2)
|
row(df[[1, 2], ["a", "c"]], df[[1, 2], "a":"c"], df[2:, [0, 1, 3]], df[2:, ::2])
shape: (2, 2)
|
shape: (2, 3)
|
shape: (2, 3)
|
shape: (2, 2)
|
行のインデックスがリストやスライスで、列のインデックスが単独の要素の場合:
Series
オブジェクトが返されます。Series
は、選択された特定の列の行データを保持します。
row(df[[1, 2], "a"], df[1:, 2])
shape: (2,)
|
shape: (3,)
|
要素の変更#
Polarsでは、[]
演算を使用してDataFrame内のデータを変更することができます。これには以下の2つの方法があります。
列リストでデータを設定する場合: 列をリストで指定し、それに対応するデータを設定します。設定するデータは、形状が一致するNumPy配列である必要があります。
# 列"a"のデータを設定
df[["a"]] = np.array([[10], [20], [30], [40]])
df
a | b | c | d |
---|---|---|---|
i32 | i64 | str | str |
10 | 5 | "x" | "a" |
20 | 6 | "y" | "b" |
30 | 7 | "z" | "c" |
40 | 8 | "w" | "d" |
# 列"a"と列"b"のデータを同時に設定
df[["a", "b"]] = np.array([[100, 200, 300, 400], [50, 60, 70, 80]]).T
df
a | b | c | d |
---|---|---|---|
i32 | i32 | str | str |
100 | 50 | "x" | "a" |
200 | 60 | "y" | "b" |
300 | 70 | "z" | "c" |
400 | 80 | "w" | "d" |
行・列を指定して特定の要素を変更する場合
行のインデックス: 整数または整数のリストで指定します。
列のインデックス: 整数または文字列で指定します。
# 単一の行と列のデータを変更
df[1, "a"] = -1
df
a | b | c | d |
---|---|---|---|
i32 | i32 | str | str |
100 | 50 | "x" | "a" |
-1 | 60 | "y" | "b" |
300 | 70 | "z" | "c" |
400 | 80 | "w" | "d" |
# 複数の行のデータを変更
df[[1, 3], "b"] = -1, -2
df
a | b | c | d |
---|---|---|---|
i32 | i32 | str | str |
100 | 50 | "x" | "a" |
-1 | -1 | "y" | "b" |
300 | 70 | "z" | "c" |
400 | -2 | "w" | "d" |