はじめに
概要
pandasを使っていたハマったことや、ハマりそうなことをクイズにしました。
問題の多くは2択です。直観で答えてみてください。
2024/04/19に社内でpandasクイズを実施しました。
動作環境
以下の環境で動作確認しました。
- pandas 2.2.2
- numpy 1.26.4
- Python 3.12.1
事前にIPythonで以下をimportしています。
In [1]: import pandas as pd
In [2]: import numpy as np
問題文の例(出力結果を答える)
IPythonで実行したときに、どのような結果になるかを選択してください。
問題となる実行文は、In [?]
で表します。
出力結果の選択肢はOut[1]
, Out[2]
のように表します。
In [?]: np.inf - np.inf
Out[1]: nan
Out[2]: 0.0
Out[3]:
raise Error
1, 2, 3のいずれかの番号を選択してください。
なお、回答は1です。
問題文の例(実行文を答える)
ある実行文に対する出力結果をOut[?]
で表します。
ある実行文の選択肢を、In[1]
, In[2]
のように表します。
In [1]: np.inf + np.inf
In [2]: np.inf - np.inf
In [3]: np.inf * np.inf
In [4]: np.inf / np.inf
Out[?]: nan
適切な実行文を1, 2, 3, 4の番号からすべてを選択してください。
なお、回答は2と4です。
データの取得に関するクイズ
問01: Seriesに対して[]
でアクセス
In [372]: s1 = pd.Series(["a","b"])
In [373]: s1
Out[373]:
0 a
1 b
dtype: object
# `[0]`は0番目の要素が返る
In [374]: s1[0]
Out[374]: 'a'
ですが...
# indexがs1と異なる場合
In [375]: s2 = pd.Series(["a","b"], index=[1,0])
In [376]: s2
Out[376]:
1 a
0 b
dtype: object
In [?]: s2[0]
# 0番目の要素が返る?
Out[1]: 'a'
# indexが0である要素が返る?
Out[2]: 'b'
適切なOuputを1つ選択してください。
答: 2
indexが0である要素が返ります。
In [377]: s2[0]
Out[377]: 'b'
問02: Seriesに対して[]
でアクセス Part2
In [559]: s3 = pd.Series(["a","b"], index=["x","y"])
In [560]: s3
Out[560]:
x a
y b
dtype: object
In [?]: s3[0]
# 0番目の要素が返る?
Out[1]: 'a'
# indexに0は存在しないからエラーが発生する?
Out[2]:
raise KeyError
適切なOutputを1つ選択してください。
答: 1
0番目の要素が返ります。
ただしFutureWarning
が発生します。将来のバージョンではindexが0である要素を返すようなので、エラーが発生すると思います。
In [561]: s3[0]
<ipython-input-561-e48481fcb92e>:1: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
s3[0]
問03: Seriesに対して.loc[]
にスライスを指定したとき
In [391]: s1 = pd.Series([4,5,6], index=["a","b","c"])
In [?]: s1.loc["a":"b"]
# index "b"は含まれる?
Out[1]:
a 4
b 5
dtype: int64
# index "b"は含まれない?
Out[2]:
a 4
dtype: int64
答: 1
スライスの終了位置は含まれます。
In [412]: s1.loc["a":"b"]
Out[412]:
a 4
b 5
dtype: int64
問04: Seriesに対して.loc[]
にスライスを指定する Part2
# indexに整数を指定
In [417]: s1 = pd.Series(["a","b","c"], index=[1,3,5])
In [418]: s1.loc[1:5]
Out[418]:
1 a
3 b
5 c
dtype: object
# indexに存在しない0を開始,6は終了に指定する
In [?]: s1.loc[0:6]
Out[1]:
1 a
3 b
5 c
dtype: object
Out[2]:
raise KeyError
適切なOutputを1つ選択してください。
答: 1
If at least one of the two is absent, but the index is sorted, and can be compared against start and stop labels, then slicing will still work as expected, by selecting labels which rank between the two 1
※ "at least one of the two is absent"とあるが、両方存在しなくても期待通り動くのはなぜ?
# indexがソートされていない場合
In [445]: s = pd.Series(["a","b","c"], index=[5,1,3])
In [446]: s.loc[0:6]
...
KeyError: 0
However, if at least one of the two is absent and the index is not sorted, an error will be raised (since doing otherwise would be computationally expensive, as well as potentially ambiguous for mixed type indexes). 2
問05: Seriesに対して[]
にスライスを指定する
In [391]: s1 = pd.Series([4,5,6], index=["a","b","c"])
In [400]: s1
Out[400]:
a 4
b 5
c 6
dtype: int64
In [?]: s1[1:]
# indexに1は存在しないからエラーになる?
Out[1]:
raise KeyError
# 1番目以降の要素が返る?
Out[2]:
b 5
c 6
dtype: int64
適切なOutputを一つ選択してください。
答: 2
[1:]
は、1番目以降の要素が返ります。
In [402]: s1[1:]
Out[402]:
b 5
c 6
dtype: int64
なお、スライスでindexを指定することもできます。
In [471]: s1["b":]
Out[471]:
b 5
c 6
dtype: int64
Seriesに対して[]
を指定したときの挙動は、__getitem__()
で定義されています。
__getitem__()
の中では、以下のような判定を行って処理しています。
-
key_is_scalar
->self._get_value(key)
-
is_integer(key) and self.index._should_fallback_to_positional
->self._values[key]
-
isinstance(key, slice)
->self._getitem_slice(key)
問06: DataFrameに対して[]
でアクセスする
# columnとindexを同じにする
In [451]: df = pd.DataFrame({"a":[1,2], "b":[3,4]}, index=["a","b"])
In [452]: df
Out[452]:
a b
a 1 3
b 2 4
In [?]: df["a"]
# column "a"が返る?
Out[1]:
a 1
b 2
Name: a, dtype: int64
# index "a"が返る?
Out[2]:
a 1
b 3
Name: a, dtype: int64
適切なOutputを1つ選択してください。
答: 1
column a
の情報が返ります。
In [473]: df["a"]
Out[473]:
a 1
b 2
Name: a, dtype: int64
問07: DataFrameに対して[]
にスライスを指定する
In [451]: df = pd.DataFrame({"a":[1,2], "b":[3,4]}, index=["a","b"])
In [452]: df
Out[452]:
a b
a 1 3
b 2 4
In [?]: df["b":]
# column "b"の情報が返る?
Out[1]:
b
a 3
b 4
# index "b"の情報が返る?
Out[2]:
a b
b 2 4
適切なOutputを1つ選択してください。
答: 2
DataFrameに対して[]
にスライスを指定すると、indexに紐づく情報が返ります。
In [474]: df["b":]
Out[474]:
a b
b 2 4
問08: Seriesをfor in
でループする
In [460]: s = pd.Series([1,2], index=["a","b"])
In [461]: s
Out[461]:
a 1
b 2
dtype: int64
In [?]: [e for e in s]
# 値が返る? list-like?
Out[1]: [1, 2]
# indexが返る? dict-like?
Out[2]: ["a", "b"]
適切なOutputを1つ選択してください。
答: 1
Seriesをfor-in
ループで回すと値が返ります。
In [467]: [e for e in s]
Out[467]: [1, 2]
Series
はdict-likeでもありlist-likeです。
Seriesオブジェクトの生成に関するクイズ
問11: Series()
でindex
引数を指定した場合
# 事前知識
In [15]: pd.Series(data={"a":1.0, "b":2.0})
Out[15]:
a 1.0
b 2.0
dtype: float64
In [?]: pd.Series(data={"a":1.0, "b":2.0}, index=["a","c"])
# `index`優先
Out[1]:
a 1.0
c NaN
dtype: float64
# `data`優先
Out[2]:
a 1.0
b 2.0
dtype: float64
# `data`と`index`のunion
Out[3]:
a 1.0
b 2.0
c NaN
dtype: float64
適切なOutputを選択してください。
答: 1
In [475]: pd.Series(data={"a":1.0, "b":2.0}, index=["a","c"])
Out[475]:
a 1.0
c NaN
dtype: float64
If an index is passed, the values in data corresponding to the labels in the index will be pulled out. 3
問12: Series()
でindex
引数を指定した場合 Part2
In [19]: pd.Series([1.0, 2.0], index=["a", "b"])
Out[19]:
a 1.0
b 2.0
dtype: float64
# dataのlengthとindexの長さ異なる場合
In [?]: pd.Series([1.0, 2.0], index=["a"])
Out[1]:
a 1.0
dtype: float64
Out[2]:
raise ValueError
適切なOutputを選択してください。
答: 2
In [475]: pd.Series(data={"a":1.0, "b":2.0}, index=["a","c"])
Out[475]:
a 1.0
c NaN
dtype: float64
In [476]: pd.Series([1.0, 2.0], index=["a"])
--------------------------------------------------------------
ValueError: Length of values (2) does not match length of index (1)
If data is an ndarray, index must be the same length as data. 4
index
引数で指定した値が優先される訳ではないようです。
問13: Series
同士の加算
In [475]: s = pd.Series([1,2], index=["a","b"])
In [480]: s1 = s.iloc[0:1]
In [481]: s1
Out[481]:
a 1
dtype: int64
In [482]: s2 = s.iloc[1:]
In [483]: s2
Out[483]:
b 2
dtype: int64
In [?]: s1 + s2
# 同じindexのものがあれば加算されるが、同じindexはないのでindexをunionしたものと同じになる?
Out[1]:
a 1
b 2
dtype: int64
# 同じindexのもの同士で加算するが、同じindexがないのでNaNとの加算になる?
Out[2]:
a NaN
b NaN
dtype: float64
# 両方に存在するindexは存在しないから、空のSereisになる?
Out[3]: Series([], dtype: float64)
適切なOutputを選択してください。
答: 2
In [486]: s1 + s2
Out[486]:
a NaN
b NaN
dtype: float64
The result of an operation between unaligned Series will have the union of the indexes involved. If a label is not found in one Series or the other, the result will be marked as missing NaN. 5
DataFrameオブジェクトの生成に関するクイズ
問21: 異なるnamedtupleで構成されたlistをpd.DataFrame()
に渡す
In [32]: from collections import namedtuple
In [33]: Point = namedtuple("Point", "x y")
In [34]: Point3D = namedtuple("Point3D", "x y z")
# 先頭の要素が`Point3D`
In [?]: pd.DataFrame([Point3D(0, 0, 0), Point(0, 0)])
Out[1]:
x y z
0 0 0 0.0
1 0 0 NaN
Out[2]:
raise ValueError
# 先頭の要素が`Point`
In [?]: pd.DataFrame([Point(0, 0), Point3D(0, 0, 0)])
Out[3]:
x y z
0 0 0 NaN
1 0 0 0.0
Out[4]:
raise ValueError
適切なOutputをすべて選択してください。
答: 1, 4
In [490]: pd.DataFrame([Point3D(0, 0, 0), Point(0, 0)])
Out[490]:
x y z
0 0 0 0.0
1 0 0 NaN
In [38]: pd.DataFrame([Point(0, 0), Point3D(0, 0, 0)])
----
ValueError: 2 columns passed, passed data had 3 columns
The field names of the first namedtuple in the list determine the columns of the DataFrame. The remaining namedtuples (or tuples) are simply unpacked and their values are fed into the rows of the DataFrame. If any of those tuples is shorter than the first namedtuple then the later columns in the corresponding row are marked as missing values. If any are longer than the first namedtuple, a ValueError is raised. 6
dictのlistを渡せばValueError
は発生しません。
In [10]: pd.DataFrame([{"x":0, "y":0}, {"x":0, "y":0, "z":0}])
Out[10]:
x y z
0 0 0 NaN
1 0 0 0.0
問22: DataFrameに対してSeries
を代入して列を追加
In [17]: df = pd.DataFrame({"x":[1.0,2.0]}, index=["a","b"])
In [18]: df
Out[18]:
x
a 1.0
b 2.0
In [19]: s = pd.Series([11.0, 13.0], index=["a","c"])
In [20]: s
Out[20]:
a 11.0
c 13.0
dtype: float64
# column 'y'を追加
In [21]: df["y"] = s
In [?]: df
# `df.index`と`s.index`のunionが新しいindexになる
Out[1]:
x y
a 1 11.0
b 2 NaN
c NaN 13.0
# `df`に存在しないindexは無視される
Out[2]:
x y
a 1.0 11.0
b 2.0 NaN
適切なOutputを1つ選択してください。
答: 2
In [532]: s = pd.Series([11.0, 13.0], index=["a","c"])
In [533]: df = pd.DataFrame({"x":[1.0,2.0]}, index=["a","b"])
In [534]: df["y"] = s
In [535]: df
Out[535]:
x y
a 1.0 11.0
b 2.0 NaN
When inserting a Series that does not have the same index as the DataFrame, it will be conformed to the DataFrame’s index: 7
DataFrameへの代入で、DataFrameのindexは変化しない(はず)ようにみえます。
問23: DataFrameに対してlist
を代入して列を追加
In [29]: df = pd.DataFrame({"x":[1.0,2.0]}, index=["a","b"])
In [30]: df
Out[30]:
x
a 1.0
b 2.0
# column 'y' を追加
In [31]: df["y"] = [11.0, 12.0]
In [?]: df["y"]
# listにはindexが存在しないから、df.indexに対応しないのでY列はNaN ?
Out[1]:
a NaN
b NaN
Name: y, dtype: float64
# よしなに追加できるはず?
Out[2]:
a 11.0
b 12.0
Name: y, dtype: float64
適切なOutputを1つ選択してください。
答: 2
In [536]: df = pd.DataFrame({"x":[1.0,2.0]}, index=["a","b"])
In [537]: df["y"] = [11.0, 12.0]
In [538]: df["y"]
Out[538]:
a 11.0
b 12.0
Name: y, dtype: float64
問24: DataFrameに対してSeries
を代入して列を追加 Part2
In [29]: df = pd.DataFrame({"x":[1.0,2.0]}, index=["a","b"])
In [30]: df
Out[30]:
x
a 1.0
b 2.0
In [30]: s = pd.Series([11.0, 12.0])
In [31]: s
Out[31]:
0 11.0
1 12.0
dtype: float64
# column 'y'を追加
In [32]: df["y"] = s
In [?]: df["y"]
# Seriesのindexはdf.indexに対応しないのでY列はNaN?
Out[1]:
a NaN
b NaN
Name: y, dtype: float64
# よしなに追加できるはず?
Out[2]:
a 11.0
b 12.0
Name: y, dtype: float64
適切なOutputを1つ選択してください。
答: 1
In [544]: df = pd.DataFrame({"x":[1.0,2.0]}, index=["a","b"])
In [546]: s = pd.Series([11.0, 12.0])
In [547]: df["y"] = s
In [548]: df["y"]
Out[548]:
a NaN
b NaN
Name: y, dtype: float64
問25: DataFrame
とSeries
の加算
In [41]: df = pd.DataFrame([[1,2],[3,4]])
In [42]: df
Out[42]:
0 1
0 1 2
1 3 4
In [43]: s = pd.Series([10,20])
In [44]: s
Out[44]:
0 10
1 20
dtype: int64
In [?]: df + s
# 加算結果は、SeriesのindexとDataFrameのcolumnが対応している?
Out[1]:
0 1
0 11 22
1 13 24
# 加算結果は、SeriesのindexとDataFrameのindexが対応している?
Out[2]:
0 1
0 11 12
1 23 24
適切なOutputを1つ選択してください。
答: 1
加算結果は、SeriesのindexとDataFrameのcolumnが対応しています。
In [502]: df + s
Out[502]:
0 1
0 11 22
1 13 24
When doing an operation between DataFrame and Series, the default behavior is to align the Series index on the DataFrame columns, thus broadcasting row-wise. 8:
add()
メソッドのaxis
引数を指定すれば、SeriesのindexをDataFrameのindexに対応させることもできます。
In [51]: df.add(s, axis="index")
Out[51]:
0 1
0 11 12
1 23 24
比較演算に関するクイズ
問31: ==
によるSeriesの比較
In [100]: s = pd.Series(["foo", "bar"])
In [101]: s
Out[101]:
0 foo
1 bar
dtype: object
# スカラー値との比較
In [1]: s == "foo"
# listとの比較
In [2]: s == ["foo", "bar"]
# numpy arrayとの比較
In [3]: s == np.array(["foo", "bar"])
# 長さの異なるpd.Seriesとの比較
In [4]: s == pd.Series(["foo"])
Errorが発生するInputはすべて選択してください。
答: 4のみ
In [1]: s == "foo"
Out[1]:
0 True
1 False
dtype: bool
In [2]: s == ["foo", "bar"]
Out[2]:
0 True
1 True
dtype: bool
In [3]: s == np.array(["foo", "bar"])
Out[3]:
0 True
1 True
dtype: bool
In [4]: s == pd.Series(["foo"])
---------------------------------------------------------------------------
...
ValueError: Can only compare identically-labeled Series objects
スカラー値やarray-likeなオブジェクトと比較できます。ただし、比較する際は長さを一致させる必要があります。9
問32: ==
によるSeriesの比較 Part2
In [203]: s = pd.Series([1.0, np.nan])
In [204]: s
Out[204]:
0 1.0
1 NaN
dtype: float64
In [?]: s == s
Out[1]: True
Out[2]: False
Out[3]:
0 True
1 True
dtype: bool
Out[4]:
0 True
1 False
dtype: bool
適切なOutputを一つ選択してください。
答: 4
要素同士を比較した結果(bool)のSeriesが返ります。ただしnp.nan == np.nan
はFalseが返ることに注意してください。
In [207]: s == s
Out[207]:
0 True
1 False
dtype: bool
In [208]: np.nan == np.nan
Out[208]: False
同じ要素を持っているかどうかはequals()
で確認できます。10
In [210]: s.equals(s)
Out[210]: True
問33: Seriesに対するin演算子の結果
In [222]: s = pd.Series([0,1], index=["a","b"])
In [223]: s
Out[223]:
a 0
b 1
dtype: int64
# index "a"が含まれているかを確認できる?
In [1]: "a" in s
# value "0"が含まれているかを確認できる?
In [2]: 0 in s
Out[?]: True
適切なInputを一つ選んでください。
答: 1
Using the Python in operator on a Series tests for membership in the index, not membership among the values.
If this behavior is surprising, keep in mind that using in on a Python dictionary tests keys, not values, and Series are dict-like. 1
In [227]: "a" in s
Out[227]: True
In [242]: 0 in s
Out[242]: False
# dictでも確認する
In [228]: d = {"a":1,"b":2}
In [229]: "a" in d
Out[229]: True
値が含まれているかはSeries.isin()
で確認できます。
In [233]: s.isin([0])
Out[233]:
a True
b False
dtype: bool
問34: DataFrameに対するin演算子の結果
In [237]: df = pd.DataFrame({"a":[1,2], "b":[3,4]}, index=["x","y"])
In [238]: df
Out[238]:
a b
x 1 3
y 2 4
# index "x"が含まれているかを確認できる?
In [1]: "x" in df
# column "a"が含まれているかを確認できる?
In [2]: "a" in df
# value "1"が含まれているかを確認できる?
In [3]: 1 in df
Out[*]: True
適切なInputを一つ選んでください。
答: 2
In [239]: "x" in df
Out[239]: False
In [240]: "a" in df
Out[240]: True
In [241]: 1 in df
Out[241]: False
その他のクイズ
問41: 欠損値を含むデータのsum
In [1]: np.sum([1, 2, np.nan])
In [2]: pd.Series([1, 2, np.nan]).sum()
In [3]: np.sum(pd.Series([1, 2, np.nan]))
Out[*]: 3.0
適切なInputをすべて選んでください。
答: 2,3
Series.sum()
などpandasのdescriptive statisticsなメソッドは、デフォルトでは欠損値を無視します。
In [1]: np.sum([1, 2, np.nan])
Out[1]: nan
In [2]: pd.Series([1, 2, np.nan]).sum()
Out[2]: 3.0
In [3]: np.sum(pd.Series([1,2,np.nan]))
Out[3]: 3.0
skipna=False
を指定すれば、欠損値は無視されず、結果はnp.nan
になります。
In [553]: pd.Series([1, 2, np.nan]).sum(skipna=False)
Out[553]: nan
※ np.sum(pd.Series([1,2,np.nan]))
の結果がnp.nan
でなく3.0
である原因は分かりませんでした。
Note that by chance some NumPy methods, like mean, std, and sum, will exclude NAs on Series input by default 11
問42: 分散を求める
# numpyで分散を求める
In [?]: np.array([0]).var()
Out[1]: 0.0
Out[2]: nan
# pandasで分散を求める
In [?]: pd.Series([0]).var()
Out[3]: 0.0
Out[4]: nan
適切なOutputを複数選択してください。
答: 1, 4
numpyのvar()
ではddof
(自由度)引数のデフォルト値は0なのに対して、pandasのvar()
ではデフォルト値は1です。
In [570]: np.array([0]).var()
Out[570]: 0.0
In [572]: pd.Series([0]).var()
Out[572]: nan
In [574]: pd.Series([0]).var(ddof=0)
Out[574]: 0.0
問43: agg()
にnp.var
を渡す
agg()
には集約したい関数や関数名を渡すことができます。
In [6]: pd.Series([1,2]).agg(["sum"])
Out[6]:
sum 3
dtype: int64
In [?]: pd.Series([0]).agg([np.var])
Out[1]:
var 0.0
dtype: float64
Out[2]:
var NaN
dtype: float64
適切なOutputを選択してください。
答: 2
np.var
を実行しているように見えますが、実はpandasのvar()
が呼ばれています。ただし、この挙動は将来のバージョンで変わります。
In [3]: pd.Series([0]).agg([np.var])
<ipython-input-3-bcf21703d287>:1: FutureWarning: The provided callable <function var at 0x7f71fa7d4c20> is currently using Series.var. In a future version of pandas, the provided callable will be used directly. To keep current behavior pass the string "var" instead.
pd.Series([0]).agg([np.var])
まとめ
教訓
- Seriesはlist-likeでもありdict-likeであることを意識する
- Seriesを扱うときはindexを意識する
- SeriesまたDataFrameに対して
[]
でアクセスすると、直観的でない挙動に遭遇するかもしれない。できるだけ.loc[]
や.iloc[]
を使った方がよい
-
https://pandas.pydata.org/docs/user_guide/indexing.html#slicing-with-labels ↩ ↩2
-
https://pandas.pydata.org/docs/user_guide/indexing.html#slicing-with-labels ↩
-
https://pandas.pydata.org/docs/user_guide/dsintro.html#series-is-dict-like ↩
-
https://pandas.pydata.org/docs/user_guide/dsintro.html#series ↩
-
https://pandas.pydata.org/docs/user_guide/dsintro.html#vectorized-operations-and-label-alignment-with-series ↩
-
https://pandas.pydata.org/docs/user_guide/dsintro.html#from-a-list-of-namedtuples ↩
-
https://pandas.pydata.org/docs/user_guide/dsintro.html#column-selection-addition-deletion ↩
-
https://pandas.pydata.org/docs/user_guide/dsintro.html#indexing-selection ↩
-
https://pandas.pydata.org/docs/user_guide/basics.html#comparing-array-like-objects ↩
-
https://pandas.pydata.org/docs/user_guide/basics.html#comparing-if-objects-are-equivalent ↩
-
https://pandas.pydata.org/docs/user_guide/basics.html#descriptive-statistics ↩