Pandas 集中講座 その2 選択

Pandasライブラリを覚えたい!公式「10 Minutes to pandas」をモチーフに使って実際に動作させ学習する。第2回 選択

Pandas 集中講座(2)

Pandas 集中講座 その1 - chiyoh’s blog
Pandas 集中講座 その2 - chiyoh’s blog
Pandas 集中講座 その3 - chiyoh’s blog
Pandas 集中講座 その4 - chiyoh’s blog
Pandas 集中講座 その5 - chiyoh’s blog
Pandas 集中講座 その6 - chiyoh’s blog

Pandasとは、なにか?

 初め考えていたのは、EXCELシートも扱えるPythonスプレッドシートライブラリ。 VBAの代わりにマクロをPython上で実行できる便利なものと考えていた。

Pandasを実際に使ってみると

 始めて使ったのは、テキストデータをEXCELのxlsxに変換しておきたかったから。実際に変換してみると思ったより難しい(実際には、我々がEXCEL表計算としてとらえていなくて計算機能と印刷機能を持つワープロとして使っているのが原因)。また、Pandasも高機能なので出来ることが多く大いに迷ったが、それでもEXCELファイルの書き出しに成功したのでこれは便利だ!ということでこれを機に少し使ってみようと考えた。

公式「10 Minutes to pandas」を実際にやってみる (2/6)

 Pandasを覚える!さて、グーグル先生に教えてもらえば大抵のことは分かるのだが、それが今の最適解なのか?と考えるとそうではない。グーグル先生のサーチ機能は、カンニングペーパーであり、単語帳である。欲しい答えを最小ステップでたどり着くための方法。例えるなら、望遠鏡、又は顕微鏡で拡大されたエリアで見つけた希望(答え)。なぜそうなるのか?なぜそう書けば動くのか?など、答えに対する成り立ちがすっぽり抜けてしまうのである。最近のPython環境は、Jupyter Notebookで敷地も下がって来ている。入門書や学習本を眺めて図を見てなんとなくへーとか言って分かった気になって何も残らないパターンになりかねない。今回選んだ方法は、グーグル先生の支援のもと公式のチュートリアル以前のPandas概要説明『10 Minutes to pandas』を上から順にJupyter Notebookを使い実行していってみようと思う。これにより、学習講座のWorkshop形式で講師が腹痛で欠席したので自己学習してなさい版くらいの知識に残ることを期待。実行した結果をここに残し、その時のメモ書きとエッセンスと晒してみる。

pandas.pydata.org

【解説】公式「10 Minutes to pandas」をJupyter Notebookを使って動作させながら確認し、突っ込みを入れる!(この文章を見る価値としては、原書が何をやっているのかが分かる、、、分かるはずである)

公式「10 Minutes to pandas」を実際にやってみる

3.選択(Selection)

注意:選択と設定のための標準的なPython / Numpy式は直感的でインタラクティブな作業には便利ですが、プロダクションコードのためには、最適化されたpandasデータアクセスメソッド、.at、.iat、.locおよび.ilocをお勧めします。

インデックス作成のドキュメントインデックス作成とデータの選択およびMultiIndex / Advanced Indexingを参照してください。

【解説】Pandasで最適化されたアクセス方法を用意してあるのでそっちを使ってね。ということか、Python / Numpy式が何かは分かりにくいが今私たちが思い浮かぶアクセスだよね?付けて選択する方法とかでx,y座標的につかったりするやりかた。[::-1]とかもかな

配列形式な選択 ()

単一の列を選択すると、単一Seriesオブジェクトとなりdf.Aでもあります。

df['A']
2019-05-01   -0.149943
2019-05-02    0.108830
2019-05-03   -0.627742
2019-05-04   -0.000103
2019-05-05   -0.064446
2019-05-06   -0.438960
Freq: D, Name: A, dtype: float64
df.A
2019-05-01   -0.149943
2019-05-02    0.108830
2019-05-03   -0.627742
2019-05-04   -0.000103
2019-05-05   -0.064446
2019-05-06   -0.438960
Freq: D, Name: A, dtype: float64
type(df['A'])
pandas.core.series.Series

【実況】Numpy形式か?

間を選択すると行がスライスされます。

df[0:3]
A B C D
2019-05-01 -0.149943 0.785767 0.622169 1.553862
2019-05-02 0.108830 1.195339 1.791636 1.337328
2019-05-03 -0.627742 -0.601345 0.201793 -0.685570
df['20190502':'20190504']
A B C D
2019-05-02 0.108830 1.195339 1.791636 1.337328
2019-05-03 -0.627742 -0.601345 0.201793 -0.685570
2019-05-04 -0.000103 -0.482461 -1.541615 -0.710261

【解説】column列を選択するのには、[column名]でよく、index行選択には[index行:index行]でする

a=[0,1,2,3,4,5]
a[0:2]
[0, 1]

【解説】だよね?Numpyと少し動作が違うNumpyでは[start:end:step]がイタレーション的forループで作られているから条件式部分がcount<endで定義されているのでendは含まれない。[start:end:step]分かりにくい元か[init:times:step]が適切init初期値、times何回ループするか、stepループ時の増減値なのでPandasの直感的なここからここまでな[from:to]みたいなとは別になる

【余談】なんで別にした?と思ったがindexが数値で連続性を持っているとは限らないので次のデータが+1で指定できないためか

df['20190503']
KeyError: '20190503'

【解説】DatetimeIndexなんて特別なクラスを使っているから'2019-05-03'を'20190503'と省略して書けるのね。それは置いておいて、1つ選ぶのに直接呼び出したらエラーになった。これはスライスして同じ値を入れることで回避できる

df['2019-05-03':'2019-05-03']
A B C D
2019-05-03 -0.627742 -0.601345 0.201793 -0.68557

ラベルによる選択(.loc[]) 行選択

ラベルによる選択の詳細を参照してください。

ラベルを使って断面を取得するには:

df.loc[dates[0]]
A   -0.149943
B    0.785767
C    0.622169
D    1.553862
Name: 2019-05-01 00:00:00, dtype: float64

【実況】dates[0]ってなんだ?dfを定義したときに使った変数だけど。10分講座だから覚えているよね?むりー

dates = pd.date_range('20190501', periods=6)
df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD'))
df
A B C D
2019-05-01 0.451080 1.378920 -0.897954 0.382555
2019-05-02 -0.250102 -1.663388 0.010251 -1.531969
2019-05-03 -1.061661 0.248446 -1.246751 1.907569
2019-05-04 0.064499 0.386742 -0.841549 -1.483970
2019-05-05 -0.694038 -0.496932 0.070118 -0.056977
2019-05-06 -0.422955 0.824024 0.654380 0.202406
df.loc[dates[0]]
A    0.451080
B    1.378920
C   -0.897954
D    0.382555
Name: 2019-05-01 00:00:00, dtype: float64
dates
DatetimeIndex(['2019-05-01', '2019-05-02', '2019-05-03', '2019-05-04',
               '2019-05-05', '2019-05-06'],
              dtype='datetime64[ns]', freq='D')
dates[0]
Timestamp('2019-05-01 00:00:00', freq='D')

【解説】indexに使ったDatetimeIndex形式はインスタンス化するときに'20190501'として日まで入力しているが実際は秒まで認識している。EXCELでいうところのシリアル値(int64)で内部処理されている

df.loc[dates[-1]]
A   -0.422955
B    0.824024
C    0.654380
D    0.202406
Name: 2019-05-06 00:00:00, dtype: float64
df.loc['2019-05-03']
A   -1.061661
B    0.248446
C   -1.246751
D    1.907569
Name: 2019-05-03 00:00:00, dtype: float64

【解説】[]では、ダメでしたが.loc[]では、OKのようです

df.loc[:]
A B C D
2019-05-01 0.451080 1.378920 -0.897954 0.382555
2019-05-02 -0.250102 -1.663388 0.010251 -1.531969
2019-05-03 -1.061661 0.248446 -1.246751 1.907569
2019-05-04 0.064499 0.386742 -0.841549 -1.483970
2019-05-05 -0.694038 -0.496932 0.070118 -0.056977
2019-05-06 -0.422955 0.824024 0.654380 0.202406

【解説】[:]は、index全部

ラベルで多軸を選択: 行と列選択 (.loc[ix,col] ラベルで行と列のグループにアクセス)

df.loc[:, ['A', 'B']]
A B
2019-05-01 0.451080 1.378920
2019-05-02 -0.250102 -1.663388
2019-05-03 -1.061661 0.248446
2019-05-04 0.064499 0.386742
2019-05-05 -0.694038 -0.496932
2019-05-06 -0.422955 0.824024

【実況】分かりにくいdf.loc[row_indexer] または、df.loc[row_indexer,column_indexer]なのか?

df.loc[:, ['A', 'C']]
A C
2019-05-01 0.451080 -0.897954
2019-05-02 -0.250102 0.010251
2019-05-03 -1.061661 -1.246751
2019-05-04 0.064499 -0.841549
2019-05-05 -0.694038 0.070118
2019-05-06 -0.422955 0.654380

【解説】リスト形式にすることで、columnを複数選択できる。スライスとは違う

ラベルスライスを表示すると、両方のエンドポイントが含まれます。

df.loc['20190502':'20190504', ['A', 'B']]
A B
2019-05-02 -0.250102 -1.663388
2019-05-03 -1.061661 0.248446
2019-05-04 0.064499 0.386742
df.loc[:, 'A':'B']
A B
2019-05-01 0.451080 1.378920
2019-05-02 -0.250102 -1.663388
2019-05-03 -1.061661 0.248446
2019-05-04 0.064499 0.386742
2019-05-05 -0.694038 -0.496932
2019-05-06 -0.422955 0.824024

【解説】indexだけでなくcolumnもスライスできます

返されるオブジェクトのサイズを縮小します。

df.loc['20190502', ['A', 'B']]
A   -0.250102
B   -1.663388
Name: 2019-05-02 00:00:00, dtype: float64
type(df.loc['20190502', ['A', 'B']])
pandas.core.series.Series

【解説】indexが、一つの場合column列のSeriesを返してくる。

スカラー値を取得するには

df.loc[dates[0], 'A']
0.45107999031214707
type(df.loc[dates[0], 'A'])
numpy.float64

【解説】Index行とcolumn列を1ずつ選択するとスカラー値で戻ってくる

df.loc['20190502', ['A']]
A   -0.250102
Name: 2019-05-02 00:00:00, dtype: float64
type(df.loc['20190502', ['A']])
pandas.core.series.Series
df.loc['20190502', ['A']][0]
-0.25010196341642593

【解説】1ずつ選択だが、 ['A']が複数選択形式、indexが1つなのでSeriesで戻してくる

df.loc['20190502':, ['A']]
A
2019-05-02 -0.250102
2019-05-03 -1.061661
2019-05-04 0.064499
2019-05-05 -0.694038
2019-05-06 -0.422955

【解説】複数指定方式同士にするとDataFrameになる

スカラーへの高速アクセスを得るために(前のメソッドと同等): (.at 単一アクセス)

df.at[dates[0], 'A']
0.45107999031214707

位置による選択 (.iloc[])

位置による選択の詳細を参照してください。

渡された整数の位置で選択します。

a = np.array(range(100)).reshape(10,10).T[:6,:4]
df = pd.DataFrame(a, columns=list('ABCD'))
df
A B C D
0 0 10 20 30
1 1 11 21 31
2 2 12 22 32
3 3 13 23 33
4 4 14 24 34
5 5 15 25 35

【解説】表が分かりにくいので、行が1の位、列が10の位が変化する表に差し替えた

df.iloc[3]
A     3
B    13
C    23
D    33
Name: 3, dtype: int32

【解説】[ ]指定なので配列風,行列風な指定方法です。[index]でカラムに対するデータを選択します

整数スライスで、numpy / pythonに似た動作をします。

df.iloc[3:5, 0:2]
A B
3 3 13
4 4 14

【解説】こっちが、Numpyと同じスライスの終わりが含まれないという動作ですね

整数位置のリストで、numpy / pythonスタイルに似ています。

df.iloc[[1, 2, 4], [0, 2]]
A C
1 1 21
2 2 22
4 4 24

【実況】え?そうなん?
【解説】内容に付いて、indexの1, 2, 4を選択 colの0列目と2列目を選択交差した部分を選択

list_a = [[ 0, 10, 20, 30],[ 1, 11, 21, 31],
            [ 2, 12, 22, 32],[ 3, 13, 23, 33],
            [ 4, 14, 24, 34],[ 5, 15, 25, 35]]
list_a
[[0, 10, 20, 30],
 [1, 11, 21, 31],
 [2, 12, 22, 32],
 [3, 13, 23, 33],
 [4, 14, 24, 34],
 [5, 15, 25, 35]]



list_a[[1, 2, 4], [0, 2]]
TypeError: list indices must be integers or slices, not tuple

a[[1, 2, 4], [0, 2]]
IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (3,) (2,) 

【解説】どの辺がnumpy/python風なのか分かりません

行を明示的にスライスする場合:

df.iloc[1:3, :]
A B C D
1 1 11 21 31
2 2 12 22 32

列を明示的にスライスする場合

df.iloc[:, 1:3]
B C
0 10 20
1 11 21
2 12 22
3 13 23
4 14 24
5 15 25

【解説】:(スライス)のみで全体選択をして、もう片方をIndexかcolumnを選ぶやり方ですね

明示的に値を取得するには

df.iloc[1, 1]
11

【解説】Indexとcolumnを1つに限定するとスカラー値として取り出せる

スカラーへの高速アクセスを得るために(前のメソッドと同等):

df.iat[1, 1]
11

【解説】loc[]と同様にat[]の代わりにiat[]が単一選択の高速版になります

ブール索引付け

単一列の値を使用してデータを選択する

df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD'))
df
A B C D
2019-05-01 -0.106726 -1.762983 -1.265036 -0.404320
2019-05-02 -0.690402 -0.045117 -0.684309 1.756284
2019-05-03 -0.479369 0.419347 1.396591 -0.762011
2019-05-04 0.497103 0.760560 -2.488575 -0.670358
2019-05-05 0.974668 -1.191495 -1.674198 1.348520
2019-05-06 -1.740956 0.539032 0.709385 -1.034903
df[df.A > 0]
A B C D
2019-05-04 0.497103 0.760560 -2.488575 -0.670358
2019-05-05 0.974668 -1.191495 -1.674198 1.348520

【解説】A列で0より大きいindex行を選択しなさい

df.A
2019-05-01   -0.106726
2019-05-02   -0.690402
2019-05-03   -0.479369
2019-05-04    0.497103
2019-05-05    0.974668
2019-05-06   -1.740956
Freq: D, Name: A, dtype: float64
df.A > 0
2019-05-01    False
2019-05-02    False
2019-05-03    False
2019-05-04     True
2019-05-05     True
2019-05-06    False
Freq: D, Name: A, dtype: bool

【解説】内容的には、df.A(df['A'])でA列を選択 df.A >0 A列の中で0以上のものをTrueとして判定。df[ index ]なので、dfのindexの並び順に照らし合わせてTrueの行のみを選択した

ブール条件が満たされるDataFrameから値を選択する。

df[df > 0]
A B C D
2019-05-01 NaN NaN NaN NaN
2019-05-02 NaN NaN NaN 1.756284
2019-05-03 NaN 0.419347 1.396591 NaN
2019-05-04 0.497103 0.760560 NaN NaN
2019-05-05 0.974668 NaN NaN 1.348520
2019-05-06 NaN 0.539032 0.709385 NaN
df > 0
A B C D
2019-05-01 False False False False
2019-05-02 False False False True
2019-05-03 False True True False
2019-05-04 True True False False
2019-05-05 True False False True
2019-05-06 False True True False

【解説】さらに分かりにくいdf>0なので全体に足して0より大きいがどうかを判定している。これはいい。それをdf[ index,colum ]で全体適応したのでTrueのところは選択されて数値をFlaseのところはMaskされてnp.NaN表記になったという感じか?

df.loc[pd.Timestamp(2019, 5, 7, 0)] = -10
df.loc["2019-05-08"] = -10
df['E'] = -10
df
A B C D E
2019-05-01 00:00:00 -0.106726 -1.762983 -1.265036 -0.404320 -10
2019-05-02 00:00:00 -0.690402 -0.045117 -0.684309 1.756284 -10
2019-05-03 00:00:00 -0.479369 0.419347 1.396591 -0.762011 -10
2019-05-04 00:00:00 0.497103 0.760560 -2.488575 -0.670358 -10
2019-05-05 00:00:00 0.974668 -1.191495 -1.674198 1.348520 -10
2019-05-06 00:00:00 -1.740956 0.539032 0.709385 -1.034903 -10
2019-05-07 00:00:00 -10.000000 -10.000000 -10.000000 -10.000000 -10
2019-05-08 -10.000000 -10.000000 -10.000000 -10.000000 -10

【解説】"2019-05-08"だけでは、strのindexが挟み込まれたとして識別されてしまうみたいだ

df[df > 0]
A B C D E
2019-05-01 00:00:00 NaN NaN NaN NaN NaN
2019-05-02 00:00:00 NaN NaN NaN 1.756284 NaN
2019-05-03 00:00:00 NaN 0.419347 1.396591 NaN NaN
2019-05-04 00:00:00 0.497103 0.760560 NaN NaN NaN
2019-05-05 00:00:00 0.974668 NaN NaN 1.348520 NaN
2019-05-06 00:00:00 NaN 0.539032 0.709385 NaN NaN
2019-05-07 00:00:00 NaN NaN NaN NaN NaN
2019-05-08 NaN NaN NaN NaN NaN

【解説】df[df > 0]適応するときA列から順に適応して結果をSeriesにしてそれを総合してindexとcolumを決めているのではなく単純に全体を判断して表示しているだけっぽい(indexとE列が削除されてない)

isin()フィルタリング方法を使用する:

df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD'))
df
A B C D
2019-05-01 1.111420 -0.179004 1.799883 0.569833
2019-05-02 1.040001 -0.743061 0.310026 0.121585
2019-05-03 -0.987081 -1.032944 -0.367010 -1.180204
2019-05-04 0.164185 -0.573081 -0.544974 0.859824
2019-05-05 0.769549 3.183677 -0.426652 0.700605
2019-05-06 -1.005111 0.095534 -0.842821 1.181250
df2 = df.copy()
df2['E'] = ['one', 'one', 'two', 'three', 'four', 'three']
df2
A B C D E
2019-05-01 1.111420 -0.179004 1.799883 0.569833 one
2019-05-02 1.040001 -0.743061 0.310026 0.121585 one
2019-05-03 -0.987081 -1.032944 -0.367010 -1.180204 two
2019-05-04 0.164185 -0.573081 -0.544974 0.859824 three
2019-05-05 0.769549 3.183677 -0.426652 0.700605 four
2019-05-06 -1.005111 0.095534 -0.842821 1.181250 three
df2[df2['E'].isin(['two', 'four'])]
A B C D E
2019-05-03 -0.987081 -1.032944 -0.367010 -1.180204 two
2019-05-05 0.769549 3.183677 -0.426652 0.700605 four
df2['E'].isin(['two', 'four'])
2019-05-01    False
2019-05-02    False
2019-05-03     True
2019-05-04    False
2019-05-05     True
2019-05-06    False
Freq: D, Name: E, dtype: bool
df2['E']
2019-05-01      one
2019-05-02      one
2019-05-03      two
2019-05-04    three
2019-05-05     four
2019-05-06    three
Freq: D, Name: E, dtype: object
type(df2['E'])
pandas.core.series.Series

【解説】E列を使ってフィルタする。df2['E'].isin(['two', 'four'])は、E列に対してisinを適応isinは、中にある?の関数なので'two'と'four'があるindexのみTrueして判定1列なのでSeriesが戻ってくる。それをdf2[ ]全体に適応するとTrueの行のみが選択される。

df2[df2.isin(['two', 'four'])]
A B C D E
2019-05-01 NaN NaN NaN NaN NaN
2019-05-02 NaN NaN NaN NaN NaN
2019-05-03 NaN NaN NaN NaN two
2019-05-04 NaN NaN NaN NaN NaN
2019-05-05 NaN NaN NaN NaN four
2019-05-06 NaN NaN NaN NaN NaN
df2[df2.isin(['two', 'four'])].fillna(value='')
A B C D E
2019-05-01
2019-05-02
2019-05-03 two
2019-05-04
2019-05-05 four
2019-05-06

【解説】全体適応させるとこんな感じ

設定 (追加,更新)

新しい列を設定すると、インデックスによってデータが自動的に整列されます。(追加)

df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD'))
df
A B C D
2019-05-01 1.498666 -0.143574 -0.871839 -0.106927
2019-05-02 -0.092376 0.036220 -0.146521 0.991350
2019-05-03 -0.307696 -0.051588 -0.413379 0.826141
2019-05-04 0.397105 1.731472 -1.033554 -0.311225
2019-05-05 1.376085 0.310974 0.350129 0.221216
2019-05-06 0.821727 0.389651 -1.202223 -0.354074
s1 = pd.Series([1, 2, 3, 4, 5, 6], index=pd.date_range('20190501', periods=6))
s1
2019-05-01    1
2019-05-02    2
2019-05-03    3
2019-05-04    4
2019-05-05    5
2019-05-06    6
Freq: D, dtype: int64
df['F'] = s1
df
A B C D F
2019-05-01 1.498666 -0.143574 -0.871839 -0.106927 1
2019-05-02 -0.092376 0.036220 -0.146521 0.991350 2
2019-05-03 -0.307696 -0.051588 -0.413379 0.826141 3
2019-05-04 0.397105 1.731472 -1.033554 -0.311225 4
2019-05-05 1.376085 0.310974 0.350129 0.221216 5
2019-05-06 0.821727 0.389651 -1.202223 -0.354074 6

【解説】F列にSeriesデータを代入。結果としてF列が無いので新規に追加してSeriesのデータを各indexに代入された。

s2 = pd.Series([1, 2, 3], index=pd.date_range('20190501', periods=3))
df['G'] = s2
df
A B C D F G
2019-05-01 1.498666 -0.143574 -0.871839 -0.106927 1 1.0
2019-05-02 -0.092376 0.036220 -0.146521 0.991350 2 2.0
2019-05-03 -0.307696 -0.051588 -0.413379 0.826141 3 3.0
2019-05-04 0.397105 1.731472 -1.033554 -0.311225 4 NaN
2019-05-05 1.376085 0.310974 0.350129 0.221216 5 NaN
2019-05-06 0.821727 0.389651 -1.202223 -0.354074 6 NaN

【解説】インデックスによってデータが自動的に整列されます。の部分が分からなかったけど、追加するデータがindexに足して少ないとその要素はNaNとして扱われる。Gを適応させていくときに自動調整でNaNを追加していくという意味なんでしょう

len(df.index)
6

【解説】lenメソッドでindexの長さが分かるのでデータその分のデータがあるか確認できます

ラベルによる値の設定 (更新)

df.at[dates[0], 'A'] = 0
df
A B C D F G
2019-05-01 0.000000 -0.143574 -0.871839 -0.106927 1 1.0
2019-05-02 -0.092376 0.036220 -0.146521 0.991350 2 2.0
2019-05-03 -0.307696 -0.051588 -0.413379 0.826141 3 3.0
2019-05-04 0.397105 1.731472 -1.033554 -0.311225 4 NaN
2019-05-05 1.376085 0.310974 0.350129 0.221216 5 NaN
2019-05-06 0.821727 0.389651 -1.202223 -0.354074 6 NaN
dates
DatetimeIndex(['2019-05-01', '2019-05-02', '2019-05-03', '2019-05-04',
               '2019-05-05', '2019-05-06'],
              dtype='datetime64[ns]', freq='D')

【解説】A列,dates[0]='2019-05-01'行のデータが0.0に更新されました。A列のdtypeがfloat64なので0.0になる

位置による設定値: (更新)

df.iat[0, 1] = 0
df
A B C D F G
2019-05-01 0.000000 0.000000 -0.871839 -0.106927 1 1.0
2019-05-02 -0.092376 0.036220 -0.146521 0.991350 2 2.0
2019-05-03 -0.307696 -0.051588 -0.413379 0.826141 3 3.0
2019-05-04 0.397105 1.731472 -1.033554 -0.311225 4 NaN
2019-05-05 1.376085 0.310974 0.350129 0.221216 5 NaN
2019-05-06 0.821727 0.389651 -1.202223 -0.354074 6 NaN

【解説】こっちはかんたんですね。.iat[]なので数値指定できます。

NumPy配列で代入して設定する:

df.loc[:, 'D'] = np.array([5] * len(df))
df
A B C D F G
2019-05-01 0.000000 0.000000 -0.871839 5 1 1.0
2019-05-02 -0.092376 0.036220 -0.146521 5 2 2.0
2019-05-03 -0.307696 -0.051588 -0.413379 5 3 3.0
2019-05-04 0.397105 1.731472 -1.033554 5 4 NaN
2019-05-05 1.376085 0.310974 0.350129 5 5 NaN
2019-05-06 0.821727 0.389651 -1.202223 5 6 NaN
len(df)
6

【解説】ラベル(.loc)で追加、Numpyの1次元配列をindex分作って代入しています。
【実況】あれ、そうですかdfにlen()を適応するとindexの行数が戻ってくるんですね。Numpyの場合、要素数になるのでそうかともってました

where設定ありの操作。

df2 = df.copy()
df2[df2 > 0] = -df2
df2
A B C D F G
2019-05-01 0.000000 0.000000 -0.871839 -5 -1 -1.0
2019-05-02 -0.092376 -0.036220 -0.146521 -5 -2 -2.0
2019-05-03 -0.307696 -0.051588 -0.413379 -5 -3 -3.0
2019-05-04 -0.397105 -1.731472 -1.033554 -5 -4 NaN
2019-05-05 -1.376085 -0.310974 -0.350129 -5 -5 NaN
2019-05-06 -0.821727 -0.389651 -1.202223 -5 -6 NaN

【解説】フィルタの全体適応ですね。df2[df2 > 0] = -df2は、自分に上書きしているのでその点は気を付けましょう。df2>0で0以上を調べTrue/FalseのMaskを作って該当す要素の書き換えをしています。結果としてすべてマイナス要素に代わってます。(0とNaN以外だね)

chiyoh.hatenablog.com
chiyoh.hatenablog.com

Pandas 集中講座 その1 オブジェクト作成とデータ参照

Pandasライブラリを覚えたい!公式「10 Minutes to pandas」をモチーフに使って実際に動作させ学習する。第1回 オブジェクト作成とデータ参照

Pandas 集中講座(1)

Pandas 集中講座 その1 - chiyoh’s blog
Pandas 集中講座 その2 - chiyoh’s blog
Pandas 集中講座 その3 - chiyoh’s blog
Pandas 集中講座 その4 - chiyoh’s blog
Pandas 集中講座 その5 - chiyoh’s blog
Pandas 集中講座 その6 - chiyoh’s blog

Pandasとは、なにか?

 初め考えていたのは、EXCELシートも扱えるPythonスプレッドシートライブラリ。 VBAの代わりにマクロをPython上で実行できる便利なものと考えていた。

Pandasを実際に使ってみると

 始めて使ったのは、テキストデータをEXCELのxlsxに変換しておきたかったから。実際に変換してみると思ったより難しい(実際には、我々がEXCEL表計算としてとらえていなくて計算機能と印刷機能を持つワープロとして使っているのが原因)。また、Pandasも高機能なので出来ることが多く大いに迷ったが、それでもEXCELファイルの書き出しに成功したのでこれは便利だ!ということでこれを機に少し使ってみようと考えた。

公式「10 Minutes to pandas」を実際にやってみる

 Pandasを覚える!さて、グーグル先生に教えてもらえば大抵のことは分かるのだが、それが今の最適解なのか?と考えるとそうではない。グーグル先生のサーチ機能は、カンニングペーパーであり、単語帳である。欲しい答えを最小ステップでたどり着くための方法。例えるなら、望遠鏡、又は顕微鏡で拡大されたエリアで見つけた希望(答え)。なぜそうなるのか?なぜそう書けば動くのか?など、答えに対する成り立ちがすっぽり抜けてしまうのである。最近のPython環境は、Jupyter Notebookで敷地も下がって来ている。入門書や学習本を眺めて図を見てなんとなくへーとか言って分かった気になって何も残らないパターンになりかねない。今回選んだ方法は、グーグル先生の支援のもと公式のチュートリアル以前のPandas概要説明『10 Minutes to pandas』を上から順にJupyter Notebookを使い実行していってみようと思う。これにより、学習講座のWorkshop形式で講師が腹痛で欠席したので自己学習してなさい版くらいの知識に残ることを期待。実行した結果をここに残し、その時のメモ書きとエッセンスと晒してみる。

pandas.pydata.org

【解説】公式「10 Minutes to pandas」をJupyter Notebookを使って動作させながら確認し、突っ込みを入れる!(この文章を見る価値としては、原書が何をやっているのかが分かる、、、分かるはずである)

公式「10 Minutes to pandas」を実際にやってみる (1/6)

約10分でPandasを覚える!

これはパンダの簡単な紹介で、主に新規ユーザーを対象としています。あなたはクックブックにもっと複雑なレシピを見ることができます。

import numpy as np
import pandas as pd

【実況】importこれがないと始まらない。matplotlibもそうだがpandasもnumpyライブラリを使用して作られている。Numpyすごいな。

1.オブジェクト作成

Pandasの基本オブジェクトSeriesオブジェクトの作成

データ構造の紹介セクションを参照してください。

【実況】ハマった。もともと、10分で何とかPandasの概要が分かればいいなぁの感覚なのに。リンク先のデータ構造紹介セレクションと書かれていることは「数学」の代数幾何紹介で行列についてうんたらかんたらとそう。中間試験範囲より広い内容じゃねーかよ!ってな感じで学習にはなったがおモッキリPandasの全体の概要が知りたかったんだよ!に対しては、大きく外れてしまった。要は、ここだけですでに数時間かかっている。
しかし、Numpyでもそうだったけど初めは、np.array()なんだよね。とにかくNumpyオブジェクトを作ってそれから使う方法を覚える。PandasもSeriesとDataFrameが基本だからそのオブジェクトの作り方いろいろが学べたのは非常に良かったかもしれない。

pandas.pydata.org

そうなんだと持ったら上記のリンクを参照するかこっちのリンクを chiyoh.hatenablog.com

Series値のリストを渡してを作成し、パンダにデフォルトの整数インデックスを作成させます。

Series(1次元格納庫なシーズ)

s = pd.Series([1, 3, 5, np.nan, 6, 8])
s
0    1.0
1    3.0
2    5.0
3    NaN
4    6.0
5    8.0
dtype: float64

【解説】数字の羅列がデータ化されてPandasライブラリのSeriesオブジェクトが作成された。0~5の番号はindexと呼ばれる。データ識別番号。データとは、オブジェクトを作るときに使った引数でここでは(1, 3, 5, NaN , 6, 8)のこと。dtypeは、(Pandas内部でNumpyライブラリ互換を使っている)DataTypeのことでfloat64として代入されたみたいだ。え?Intじゃねーの?

s = pd.Series([1, 3, 5, 6, 8])
s
0    1
1    3
2    5
3    6
4    8
dtype: int64

【解説】NaNのせいで、int64からfloat64にクラスチェンジしたみたいだ。NaNは、Not a Numberで非数。数字じゃない何か、Series(リスト、1次元配列のようなもの)に、途中でデータが無かった場合や、削除したデータなど、データ無を強引に0やマイナス値で誤魔化すことをしないでPandasでは、欠陥データとしてNaNを使って処理をしている。ということを紹介するためにnp.nanをリストに潜り込ませたと思われる。ちょっと強引だな。
 Windows OS でも、int32にならないで、ディフォルトがint64なのね。(Numpyとちょっと違う)

a = np.array([1, 3, 5, 6, 8])
a.dtype
dtype('int32')
s = pd.Series([1, 3, 5, 6, 8])
s.dtype
dtype('int64')

【雑談】Windowsパソコンではマイク〇ソフトの陰謀でint32が標準になってしまった。64bitパソコンなのになぁ。ちなみに、他のOSだとディフォルトint64である。

DataFrame(2次元格納庫)

DataFrame日付時刻インデックスとラベル付きの列を持つNumPy配列を渡してを作成します。

dates = pd.date_range('20190501', periods=6)
dates
DatetimeIndex(['2019-05-01', '2019-05-02', '2019-05-03', '2019-05-04',
               '2019-05-05', '2019-05-06'],
              dtype='datetime64[ns]', freq='D')
type(dates)
pandas.core.indexes.datetimes.DatetimeIndex
dates[0]
Timestamp('2019-05-01 00:00:00', freq='D')
type(dates[0])
pandas._libs.tslibs.timestamps.Timestamp

【解説】date_rangeは、日付専用のrange。作られるのは、「DatetimeIndex」つまり日付用Indexだ。よってindexとして使用できる。その要素は、Timestampで定義されている

df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD'))
df
A B C D
2019-05-01 -0.149943 0.785767 0.622169 1.553862
2019-05-02 0.108830 1.195339 1.791636 1.337328
2019-05-03 -0.627742 -0.601345 0.201793 -0.685570
2019-05-04 -0.000103 -0.482461 -1.541615 -0.710261
2019-05-05 -0.064446 0.856883 -0.745213 2.190018
2019-05-06 -0.438960 -1.841473 1.083706 -1.413025

【解説】numpyの乱数を使って6x4の行列を作る。先ほど作った日付6日Index(行ラベル)と、['A','B','C','D']の列ラベルを使ってデータフレームを作成

辞書形式を渡すことによってSeriesのようにDataFrame作成できます。

【雑談】Seriesの辞書形式の例題やってないじゃん!

pd.Series({"壱":1,"弐":2,"参":3})
壱    1
弐    2
参    3
dtype: int64

【解説】こんなやつ。DataFrameの列が1列限定はある意味Seriesと変わらないかな。逆もかSeriesが1つの系列にか扱えない複数あるとDataFrameになるともいえる

df2 = pd.DataFrame({'A': 1.,
                    'B': pd.Timestamp('20130102'),
                    'C': pd.Series(1, index=list(range(4)), dtype='float32'),
                    'D': np.array([3] * 4, dtype='int32'),
                    'E': pd.Categorical(["test", "train", "test", "train"]),
                    'F': 'foo'})
df2
A B C D E F
0 1.0 2013-01-02 1.0 3 test foo
1 1.0 2013-01-02 1.0 3 train foo
2 1.0 2013-01-02 1.0 3 test foo
3 1.0 2013-01-02 1.0 3 train foo

【解説】辞書形式を使ってDataFrameのオブジェクトを作る。ここでは、列毎に違うdtypeが入れられることをデモしている。 A列が、浮動小数点のスカラーで固定値入力の場合ほかのindexサイズに合わせて同じ値がコピーされてデータとして扱われるNumpyのブロードキャスト的な使い方。B列、日付タイプ、さっきはrangeタイプで変化していったけどここでは固定値の日付のPandas形式を紹介している。C列、Seriesオブジェクトも列指定できることをしめしている。ここで、indexの縛りをいれている。そしてdtype指定もできるよ宣言だ。D列は、Numpyの1次元配列も扱えることを示している。E列Pandasのカテゴリオブジェクトを何気なく紹介している?F列、文字列オブジェクトもブロードキャストできるよ的な。ほんと、てんこ盛りな内容ですね。

結果の列DataFrameは異なる dtypeを持ちます。

【雑談】まあ、そうでしょうよ

df2.dtypes
A           float64
B    datetime64[ns]
C           float32
D             int32
E          category
F            object
dtype: object

IPythonを使用している場合、列名(およびパブリック属性)のタブ補完は自動的に有効になります。完成する属性のサブセットは次のとおりです。

In [12]: df2.<TAB>  # noqa: E225, E999
df2.A                  df2.bool
df2.abs                df2.boxplot
df2.add                df2.C
df2.add_prefix         df2.clip
df2.add_suffix         df2.clip_lower
df2.align              df2.clip_upper
df2.all                df2.columns
df2.any                df2.combine
df2.append             df2.combine_first
df2.apply              df2.compound
df2.applymap           df2.consolidate
df2.D

あなたが見ることができるように、列はA、B、C、とD自動的にタブが完成されています。E同様にあります。残りの属性は簡潔にするために切り捨てられています。

【解説】上で作ったdf2作成したときにcolumn列A~Fしれ~っとメンバー登録されています。つまり

df2.B
0   2013-01-02
1   2013-01-02
2   2013-01-02
3   2013-01-02
Name: B, dtype: datetime64[ns]

【解説】こんな感じに使えます。

df3 = pd.DataFrame({"壱":1,"弐":2,"参":3})
ValueError: If using all scalar values, you must pass an index

【実況】英語理解してなかったので10分にしたぜ!

(ValueError:すべてのスカラー値を使用する場合は、インデックスを渡す必要があります)

df3 = pd.DataFrame({"壱":1,"弐":2,"参":[1,2,3]})
df3
0 1 2 1
1 1 2 2
2 1 2 3

【解説】うー。DataFrameとSeriesにはスカラー値(固定値)が使えます。正確にはブロードキャスト的な使い方ができます。Numpyみたいな感じです。これは、他の配列の大きさに自動調整してくれる機能ですが。全部が全部スカラー値の場合長さが決まらないのでエラーになったみたいですね。長さ1でもよさそうですがね。長さ1は[5.5]等のリスト形式にするのが正しいやり方なんでしょうね。

df3.参
0    1
1    2
2    3
Name: 参, dtype: int64

【実況】columnにもUTF-8が使えるみたいでアルファベット専用かよ!と思ったら漢字でもメンバー呼び出しできました。そうなんだぁー

2.データを見る(Series、DataFrameを参照する)

基本セクションを参照してください。

【実況】騙されません。これは、後で見るもので今見たら10分でおわりません!

これはフレームの上と下の行を表示する方法です。

df.head()
A B C D
2019-05-01 -0.149943 0.785767 0.622169 1.553862
2019-05-02 0.108830 1.195339 1.791636 1.337328
2019-05-03 -0.627742 -0.601345 0.201793 -0.685570
2019-05-04 -0.000103 -0.482461 -1.541615 -0.710261
2019-05-05 -0.064446 0.856883 -0.745213 2.190018
df.tail(3)
A B C D
2019-05-04 -0.000103 -0.482461 -1.541615 -0.710261
2019-05-05 -0.064446 0.856883 -0.745213 2.190018
2019-05-06 -0.438960 -1.841473 1.083706 -1.413025

【実況】なるほど、headとtailで頭と尻尾。つまり、行の始めと終わりを部分的に取り出す関数ですね。

行(index)、列(columns)を表示します。

df.index
DatetimeIndex(['2019-05-01', '2019-05-02', '2019-05-03', '2019-05-04',
               '2019-05-05', '2019-05-06'],
              dtype='datetime64[ns]', freq='D')
df.columns
Index(['A', 'B', 'C', 'D'], dtype='object')
type(df.index),type(df.columns)
(pandas.core.indexes.datetimes.DatetimeIndex, pandas.core.indexes.base.Index)
list(df.columns)
['A', 'B', 'C', 'D']
np.array(df.columns)
array(['A', 'B', 'C', 'D'], dtype=object)

【解説】Indexとcolumnsを取り出す方法ですね。取り出しても独自オブジェクトタイプになっております。list()化、ndarray化はできるのでいろいろ使えると思います。

Numpy配列に変換

DataFrame.to_numpy()は、NumPy配列に変換します。DataFrameデータ型が異なる列があると、操作が高負荷になることがあります。PandasとNumPyの基本的な違いは、NumPy配列は配列全体に対して1つのdtypeを持ち、一方、pandas DataFrameは列ごとに1つのdtypeを持ちます。DataFrame.to_numpy()を呼び出すと 、パンダは DataFrame内のすべての dtypeを保持できるNumPy dtypeを見つけます。結局これはobjectPythonオブジェクトにすべての値をキャストすることを必要とします。これはDataFrameすべての浮動小数点値のDataFrame.to_numpy()が高速であり、データをコピーする必要がないためです。

【解説】書いてある通りですが、ここにはNumpyとDataFrameの住み分けが書かれているように思います。Numpyは中身を統一して高速に処理が目的でDataFrameはいろいろなデータを整理しながら使うのが目的。

%time df.to_numpy()
Wall time: 0 ns





array([[-1.49942925e-01,  7.85767116e-01,  6.22168717e-01,
         1.55386197e+00],
       [ 1.08830080e-01,  1.19533852e+00,  1.79163625e+00,
         1.33732841e+00],
       [-6.27742453e-01, -6.01344643e-01,  2.01792873e-01,
        -6.85569839e-01],
       [-1.03391437e-04, -4.82460968e-01, -1.54161499e+00,
        -7.10261450e-01],
       [-6.44463926e-02,  8.56883212e-01, -7.45212590e-01,
         2.19001751e+00],
       [-4.38960300e-01, -1.84147291e+00,  1.08370615e+00,
        -1.41302507e+00]])

以下のためにdf2、DataFrame複数のdtypesで、 DataFrame.to_numpy()比較的高価(処理が手間で時間がかかるの意味)です。

%time df2.to_numpy()
Wall time: 0 ns





array([[1.0, Timestamp('2013-01-02 00:00:00'), 1.0, 3, 'test', 'foo'],
       [1.0, Timestamp('2013-01-02 00:00:00'), 1.0, 3, 'train', 'foo'],
       [1.0, Timestamp('2013-01-02 00:00:00'), 1.0, 3, 'test', 'foo'],
       [1.0, Timestamp('2013-01-02 00:00:00'), 1.0, 3, 'train', 'foo']],
      dtype=object)

【解説】dfは、float64で固定なのでデータを取り出しNumpyにcastすれば終わり。それに対してdf2はいろいろなdtypeがあり齟齬が出ないように調整しつつ、objectタイプにして1つ1つの要素毎にcastしてオブジェクトを生成していくので処理コストがかかっているという意味です。

【注意】Numpyは、オブジェクト型は許容しても、ndarrayは、値のdtypeがバラバラなのは許しません。**

注意:DataFrame.to_numpy()出力にインデックスラベルや列ラベルを含めません。

describe() データの簡単な統計要約を表示します。

df.describe()
A B C D
count 6.000000 6.000000 6.000000 6.000000
mean -0.195394 -0.014548 0.235413 0.378725
std 0.281479 1.163155 1.217505 1.490613
min -0.627742 -1.841473 -1.541615 -1.413025
25% -0.366706 -0.571624 -0.508461 -0.704089
50% -0.107195 0.151653 0.411981 0.325879
75% -0.016189 0.839104 0.968322 1.499729
max 0.108830 1.195339 1.791636 2.190018

【実況】へーこういうのすごいね。(データ解析って感じ)

df2.describe()
A C D
count 4.0 4.0 4.0
mean 1.0 1.0 3.0
std 0.0 0.0 0.0
min 1.0 1.0 3.0
25% 1.0 1.0 3.0
50% 1.0 1.0 3.0
75% 1.0 1.0 3.0
max 1.0 1.0 3.0

【解説】df2は、いろんなdtypeが入っているDataFrameですね。実行すると数値のカラムのみ表示されてます

データを転置する:

df.T
2019-05-01 00:00:00 2019-05-02 00:00:00 2019-05-03 00:00:00 2019-05-04 00:00:00 2019-05-05 00:00:00 2019-05-06 00:00:00
A -0.149943 0.108830 -0.627742 -0.000103 -0.064446 -0.438960
B 0.785767 1.195339 -0.601345 -0.482461 0.856883 -1.841473
C 0.622169 1.791636 0.201793 -1.541615 -0.745213 1.083706
D 1.553862 1.337328 -0.685570 -0.710261 2.190018 -1.413025

【解説】転置行列ですね。行と列の並びが入れ替わります。
【雑談】何気に使いそう。Numpyでもlistが1次元配列に対して、indexとcolumnって1次元であってshape表記にすると(1,n),(n,1)なんだよね。(n,1)とか結構扱いにくい。無意味に'[]'を繰り返したりして。早々、df.TのTは転置(Tanchi)のTではありません。TransposeのTです。

df.transpose()
2019-05-01 00:00:00 2019-05-02 00:00:00 2019-05-03 00:00:00 2019-05-04 00:00:00 2019-05-05 00:00:00 2019-05-06 00:00:00
A -0.149943 0.108830 -0.627742 -0.000103 -0.064446 -0.438960
B 0.785767 1.195339 -0.601345 -0.482461 0.856883 -1.841473
C 0.622169 1.791636 0.201793 -1.541615 -0.745213 1.083706
D 1.553862 1.337328 -0.685570 -0.710261 2.190018 -1.413025
lst = df.T.index
lst
Index(['A', 'B', 'C', 'D'], dtype='object')
for i in lst:
    print(i,type(i))
A <class 'str'>
B <class 'str'>
C <class 'str'>
D <class 'str'>

【解説】indexは、object形式ですが中身はstrです

lst = df.T.columns
lst
DatetimeIndex(['2019-05-01', '2019-05-02', '2019-05-03', '2019-05-04',
               '2019-05-05', '2019-05-06'],
              dtype='datetime64[ns]', freq='D')
for i in lst:
    print(i,type(i))
2019-05-01 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2019-05-02 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2019-05-03 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2019-05-04 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2019-05-05 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>
2019-05-06 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'>


dir(df.T)   

【解説】転置した結果indexとcolumnsが入れ替わりました。indexのDatetimeIndexがcolumnsでもDatetimeIndexを保持しています。そして要素を取り出すとTimestampに代わるみたいですね。

軸によるソート

【解説】axis=1(or 'columns'),axis=0(or 'index') 、ascending=True(昇順,上り順,ABC順,123順)、ascending=Flase(降順,下り順,ZYX順,987順)

df.sort_index(axis=1, ascending=False)
D C B A
2019-05-01 1.553862 0.622169 0.785767 -0.149943
2019-05-02 1.337328 1.791636 1.195339 0.108830
2019-05-03 -0.685570 0.201793 -0.601345 -0.627742
2019-05-04 -0.710261 -1.541615 -0.482461 -0.000103
2019-05-05 2.190018 -0.745213 0.856883 -0.064446
2019-05-06 -1.413025 1.083706 -1.841473 -0.438960
df.sort_index(axis='index', ascending=False)
A B C D
2019-05-06 -0.438960 -1.841473 1.083706 -1.413025
2019-05-05 -0.064446 0.856883 -0.745213 2.190018
2019-05-04 -0.000103 -0.482461 -1.541615 -0.710261
2019-05-03 -0.627742 -0.601345 0.201793 -0.685570
2019-05-02 0.108830 1.195339 1.791636 1.337328
2019-05-01 -0.149943 0.785767 0.622169 1.553862

値によるソート

【解説】第一パラメータがソート対象。columnのラベルが指定できる。ascending=True(昇順,上り順,ABC順,123順)、ascending=Flase(降順,下り順,ZYX順,987順)

df.sort_values(by='B')
A B C D
2019-05-06 -0.438960 -1.841473 1.083706 -1.413025
2019-05-03 -0.627742 -0.601345 0.201793 -0.685570
2019-05-04 -0.000103 -0.482461 -1.541615 -0.710261
2019-05-01 -0.149943 0.785767 0.622169 1.553862
2019-05-05 -0.064446 0.856883 -0.745213 2.190018
2019-05-02 0.108830 1.195339 1.791636 1.337328
df.sort_values(by='B',ascending=False)
A B C D
2019-05-02 0.108830 1.195339 1.791636 1.337328
2019-05-05 -0.064446 0.856883 -0.745213 2.190018
2019-05-01 -0.149943 0.785767 0.622169 1.553862
2019-05-04 -0.000103 -0.482461 -1.541615 -0.710261
2019-05-03 -0.627742 -0.601345 0.201793 -0.685570
2019-05-06 -0.438960 -1.841473 1.083706 -1.413025

【解説】DataFrameの場合、column毎に単位その他が違う可能性大なので、方向でのソートはできない。同じ形式なのになぁというなら転置してソートするというてもある。というか、転置して列毎に意味合いを持たせるのが正しい使い方

df.T.sort_values(by='2019-05-04',ascending=False)
2019-05-01 00:00:00 2019-05-02 00:00:00 2019-05-03 00:00:00 2019-05-04 00:00:00 2019-05-05 00:00:00 2019-05-06 00:00:00
A -0.149943 0.108830 -0.627742 -0.000103 -0.064446 -0.438960
B 0.785767 1.195339 -0.601345 -0.482461 0.856883 -1.841473
D 1.553862 1.337328 -0.685570 -0.710261 2.190018 -1.413025
C 0.622169 1.791636 0.201793 -1.541615 -0.745213 1.083706

【雑談】お、いけた。str指定でOKそうだ。

df.sort_values(by='B',ascending=False).index
DatetimeIndex(['2019-05-02', '2019-05-05', '2019-05-01', '2019-05-04',
               '2019-05-03', '2019-05-06'],
              dtype='datetime64[ns]', freq=None)

【解説】ソートされたDatetimeIndexは、どうなっているかと思ったら。freq=Noneになって並び替えされているみたいだ。

df.sort_values(['B','C'])
A B C D
2019-05-06 -0.438960 -1.841473 1.083706 -1.413025
2019-05-03 -0.627742 -0.601345 0.201793 -0.685570
2019-05-04 -0.000103 -0.482461 -1.541615 -0.710261
2019-05-01 -0.149943 0.785767 0.622169 1.553862
2019-05-05 -0.064446 0.856883 -0.745213 2.190018
2019-05-02 0.108830 1.195339 1.791636 1.337328

【解説】複数列指定することにより、同着のデータでの順位決めのために第二keyなどを設定できる

chiyoh.hatenablog.com

Pandas のSeriesとDataFrame

Pandas(データ構造)

Pandasのデータ構造はどうなっているのか? * Series(ラベル付きデータ) * DataFrame(ラベル付きデータが複数)

です。

Series (シリーズ)

直列、連続など、繋がりのある何かの事ですね。Pandasは、スプレッドシート(表計算)と思っていたが思想が違うみたいだ。Seriesが基本でそれの拡張がDataFrameであるのかな。

インデックス(index)、索引、目次だったり、見出しだったりいろいろ意味があるがここではシリーズとして代入する値のラベル(label)
データ(data)、整数、文字列、浮動小数点数Pythonオブジェクトなど値のこと シリーズ(Series)、1次元のラベル付き配列で軸ラベルはまとめてインデックスという。

s = pd.Series(data, index=index)
例として、インデックスのラベルとして駅名、値として東京からの営業キロ。もちろん関係性(紐づいている)があります。を使ってシリーズを作ります

index=["東京","品川","新横浜","名古屋","京都","新大阪","新神戸","姫路","岡山","福山","広島","徳山","新山口","小倉","博多"]
data=[0.0,6.8,28.8,366.0,513.6,552.6,589.5,644.3,732.9,791.2,894.2,982.7,1027,1107.7,1174.9]
s = pd.Series(data,index=index)
s
東京        0.0
品川        6.8
新横浜      28.8
名古屋     366.0
京都      513.6
新大阪     552.6
新神戸     589.5
姫路      644.3
岡山      732.9
福山      791.2
広島      894.2
徳山      982.7
新山口    1027.0
小倉     1107.7
博多     1174.9
dtype: float64
s.index
Index(['東京', '品川', '新横浜', '名古屋', '京都', '新大阪', '新神戸', '姫路', '岡山', '福山', '広島',
       '徳山', '新山口', '小倉', '博多'],
      dtype='object')
pd.Series(data)
0        0.0
1        6.8
2       28.8
3      366.0
4      513.6
5      552.6
6      589.5
7      644.3
8      732.9
9      791.2
10     894.2
11     982.7
12    1027.0
13    1107.7
14    1174.9
dtype: float64

indexがあるほうが分かりやすいですね。インデックスは、Str文字列なのでオブジェクト型になるみたいです。indexがなくても数値の羅列だけでもシリーズは作れます。測定(measure)データや記録(record)データなどを代入できるということですね。

Seriesのdata

をdataとして扱える Seriesにはindexが必要、Seriesを作るときindexを指定しないと0から始まる数値の番号が付く。 ゆえに、辞書の場合、keyとvalueがindex,dataの関係で1対1。1次元配列の場合は、指定するindexと配列数が同じサイズ出ないとErrorになる。スカラー値の場合同じ値の連続という(ブロードキャスト)意味もあるのでindexが指定されていればそれだけの分のデータをスカラー値の同じ値で埋めるのでErrorにはならない。

配列をもとに作る

import numpy as np
s = pd.Series(np.arange(0,3*10,3))
s
0     0
1     3
2     6
3     9
4    12
5    15
6    18
7    21
8    24
9    27
dtype: int32



s = pd.Series(np.arange(0,3*10,3),index=['A','B','C'])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-15-763913bcc66a> in <module>
----> 1 s = pd.Series(np.arange(0,3*10,3),index=['A','B','C'])

h:\Anaconda3\lib\site-packages\pandas\core\series.py in __init__(self, data, index, dtype, name, copy, fastpath)
    247                             'Length of passed values is {val}, '
    248                             'index implies {ind}'
--> 249                             .format(val=len(data), ind=len(index)))
    250                 except TypeError:
    251                     pass

ValueError: Length of passed values is 10, index implies 3

indexとdata数がミスマッチすると「ValueError」になる

s = pd.Series(np.arange(0,3*10,3),index=['A','B','C','D','E','F','G','H','I','J'])
s
A     0
B     3
C     6
D     9
E    12
F    15
G    18
H    21
I    24
J    27
dtype: int32
s = pd.Series([0,3,6,9,12,15,18,21,24,27],index=['A','B','C','D','E','F','G','H','I','J'])
s = pd.Series([0,3,6,9,12,15,18,21,24,27])
s = pd.Series([0,3,6,9,12,15,18,21,24,27],index=['A','A','A','A','A','A','A','A','A','A'])
s
A     0
A     3
A     6
A     9
A    12
A    15
A    18
A    21
A    24
A    27
dtype: int64

index値が一意(ほかの値とダブル、被る)でなくてもErrorにはならないがサポートされてないメソッドもある。

辞書をもとに作る

x3 = {}
x3['3x1'] = 3
x3['3x2'] = 6
x3['3x3'] = 9
x3['3x4'] = 12
x3['3x5'] = 15
pd.Series(x3)
3x1     3
3x2     6
3x3     9
3x4    12
3x5    15
dtype: int64
pd.Series({"壱":1,"弐":2,"参":3})
壱    1
弐    2
参    3
dtype: int64

データが辞書形式の場合、PythonとPandasのVersionによって、Seriesに登録される順番が変わる。知っての通り辞書型はindex(呼び出しオブジェクト)が自由で処理のスピードの観点からindex形式が数値だけだとしてもソートした順番で内部処理しているわけではない。'list(dict)','sorted(dict)'のどちらをとるかの違い。前者が代入順、後者がkeyをソートした順になっている。バージョンによってSeriesに代入される順が変わるので注意が必要。

辞書形式を使うときにもindex指定ができる。index指定をすると辞書型変数から必要なものだけをSeries化できる

di = {"壱":1,"弐":2,"参":3,'一':1,'二':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9}
pd.Series(di,["壱","弐","参","九","十","壱","壱"])
壱    1.0
弐    2.0
参    3.0
九    9.0
十    NaN
壱    1.0
壱    1.0
dtype: float64

diの辞書型からindexに登録したいものだけをリスト化してindexに入れることができる。ただし辞書に登録されてないものをdata化しようとするとErrorではなくNaN(数値ではない)として登録される。本来の意味ではなく該当値がないという意味である。

di2 = dict([(di[x],x) for x in list(di)])
di2
{1: '一', 2: '二', 3: '三', 4: '四', 5: '五', 6: '六', 7: '七', 8: '八', 9: '九'}
pd.Series(di2,[7,8,9,10])
7       七
8       八
9       九
10    NaN
dtype: object

たしかにオブジェクトは、'Not a Number'非数であるけどさ

スカラー値をもとに作る

スカラー値をもとに作るとindexの長さに一致するように値を入力する。

pd.Series(1)
0    1
dtype: int64
pd.Series(1,index=list("ABCDEF"))
A    1
B    1
C    1
D    1
E    1
F    1
dtype: int64

空のシリーズ

pd.Series()
Series([], dtype: float64)

Numpyな動作

NumpyのndarrayとSeriesは、似た動作をする。

s = pd.Series([0,3,6,9,12,15,18,21,24,27],index=['A','B','C','D','E','F','G','H','I','J'])
s[0]
0
s[:5]
A     0
B     3
C     6
D     9
E    12
dtype: int64
s[s> s.median()]
F    15
G    18
H    21
I    24
J    27
dtype: int64
s[[4,3,1]]
E    12
D     9
B     3
dtype: int64
np.pi*s**2
A       0.000000
B      28.274334
C     113.097336
D     254.469005
E     452.389342
F     706.858347
G    1017.876020
H    1385.442360
I    1809.557368
J    2290.221044
dtype: float64

Numpyのように pandas Series は、dtype(データタイプ)を持っている

s.dtype
dtype('int64')

このdtypeは、Numpyと同じだが一部拡張されておりExtensionDtypeとなっている。

s.array
<PandasArray>
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
Length: 10, dtype: int64
type(s)
pandas.core.series.Series
type(s.array)
pandas.core.arrays.numpy_.PandasArray

Seriesを配列として扱いたい場合はSeries.arrayを使う。使えるNumpy メソッド等が増える。この配列も一部拡張されたExtensionArrayとなっている。Seriesはndarrayに似ているが、実際のndarrayが必要な場合はSeries.to_numpy()を使用する。

s_array = s.to_numpy()
s_array
array([ 0,  3,  6,  9, 12, 15, 18, 21, 24, 27], dtype=int64)
type(s_array)
numpy.ndarray

辞書形式な動作

Seriesは、インデックス(ラベル)で値を取得および設定できるので固定サイズの辞書ともいえる。

Python 辞書形式と同じようにdataの追加、変更、削除、indexの有り無し確認などができる

s = pd.Series([0,3,6,9,12,15,18,21,24,27],index=['A','B','C','D','E','F','G','H','I','J'])
s['A']
0
s['I']
24
s
A     0
B     3
C     6
D     9
E    12
F    15
G    18
H    21
I    24
J    27
dtype: int64
s['A'] = -1
s['A']
-1
s['K'] = 33
'D' in s
True
del s['J']
'J' in s
False



s['J']
KeyError: 'J'
s
A    -1
B     3
C     6
D     9
E    12
F    15
G    18
H    21
I    24
K    33
dtype: int64

getメソッドを使うとラベルが見つからない処理が便利。getメソッドは、indexlabelがないときNoneまたは指定されたデフォルトが返される。

s.get('J')
type(s.get('J'))
NoneType
s.get('J', np.nan)
nan
s.get('J', 0)
0
s.get('J', 'データがありません')
'データがありません'

Seriesとndarrayの主な違いは、Series間の操作ではラベルに基づいてデータが自動的に整列されることです。したがって、関係するシリーズが同じラベルを持つかどうかを考慮せずに計算を書くことができます。

s[1:] + s[:-1]
A     NaN
B     6.0
C    12.0
D    18.0
E    24.0
F    30.0
G    36.0
H    42.0
I    48.0
K     NaN
dtype: float64

位置合わせされていないシリーズ間の演算の結果は、関連するインデックスの和集合になります。あるシリーズまたは別のシリーズでラベルが見つからない場合、結果は欠落としてマークされNaNます。明示的なデータアライメントを行わずにコードを記述できるようになると、対話型のデータ分析と研究に非常に大きな自由と柔軟性が与えられます。パンダデータ構造の統合されたデータ整列機能は、ラベル付きデータを扱うための関連ツールの大部分とは別にパンダを設定します。

同じインデックスがない演算は、演算できないためNaN(欠陥データ)になる。dropna関数を使って、データが欠けているラベルを削除することもできる。

s1 =  s[1:] + s[:-1]
s1.dropna()
B     6.0
C    12.0
D    18.0
E    24.0
F    30.0
G    36.0
H    42.0
I    48.0
dtype: float64

名前属性

indexのほかにもSeriesに名前を付けることができる。変数名のほかにオブジェクト内部で名前が保持される。

s = pd.Series(np.random.randn(5), name='something')
s
0   -0.284847
1   -0.368176
2    0.295479
3    0.432335
4   -0.894993
Name: something, dtype: float64

Series nameは、多くの場合自動的に割り当てられます。特に、以下に示すように、DataFrameから1Dスライスしたときなど

s1 = s.rename("different")
s1.name
'different'
s1
0   -0.284847
1   -0.368176
2    0.295479
3    0.432335
4   -0.894993
Name: different, dtype: float64
s
0   -0.284847
1   -0.368176
2    0.295479
3    0.432335
4   -0.894993
Name: something, dtype: float64
s1.name = 'original'
s1
0   -0.284847
1   -0.368176
2    0.295479
3    0.432335
4   -0.894993
Name: original, dtype: float64

DataFrame(データフレーム)

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

  • 1次元のndarrays、リスト、辞書、またはシリーズのDict
  • 2D numpy.ndarray
  • 構造化またはレコード ndarray
  • A Series
  • もう一つ DataFrame

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

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

辞書からDataFrameを作る

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

d = {'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']),
     'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}
df = pd.DataFrame(d)
df
one two
a 1.0 1.0
b 2.0 2.0
c 3.0 3.0
d NaN 4.0
 pd.DataFrame(d, index=['d', 'b', 'a'])
one two
d NaN 4.0
b 2.0 2.0
a 1.0 1.0
pd.DataFrame(d, index=['d', 'b', 'a'], columns=['two', 'three'])
two three
d 4.0 NaN
b 2.0 NaN
a 1.0 NaN

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

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

df.index
Index(['a', 'b', 'c', 'd'], dtype='object')
df.columns
Index(['one', 'two'], dtype='object')

ndarraysはすべて同じ長さでなければなりません。インデックスが渡される場合、インデックスも明らかに配列と同じ長さでなければなりません。インデックスが渡されなかった場合、結果は次のようrange(n)になります。ここnで、は配列の長さです。

d = {'one': [1., 2., 3., 4.],
     'two': [4., 3., 2., 1.]}
pd.DataFrame(d)
one two
0 1.0 4.0
1 2.0 3.0
2 3.0 2.0
3 4.0 1.0
pd.DataFrame(d, index=['a', 'b', 'c', 'd'])
one two
a 1.0 4.0
b 2.0 3.0
c 3.0 2.0
d 4.0 1.0

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

この場合は、一連の配列と同じように処理されます。

data = np.zeros((2, ), dtype=[('A', 'i4'), ('B', 'f4'), ('C', 'a10')])
data[:] = [(1, 2., 'Hello'), (2, 3., "World")]
data
array([(1, 2., b'Hello'), (2, 3., b'World')],
      dtype=[('A', '<i4'), ('B', '<f4'), ('C', 'S10')])
df = pd.DataFrame(data)
df
A B C
0 1 2.0 b'Hello'
1 2 3.0 b'World'
df.index
RangeIndex(start=0, stop=2, step=1)
df.index.dtype
dtype('int64')
df.columns
Index(['A', 'B', 'C'], dtype='object')
df.columns.dtype
dtype('O')
pd.DataFrame(data, columns=['C', 'A', 'B'])
C A B
0 b'Hello' 1 2.0
1 b'World' 2 3.0
pd.DataFrame(data, index=['first', 'second'])
A B C
first 1 2.0 b'Hello'
second 2 3.0 b'World'

注意 DataFrameは、2次元のNumPy ndarrayのように機能することを目的としていません。 DataFrameは、純粋なNumPy ndarrayをラベル付きで扱うのではなく、各カラムにいろいろなSeriesが入っていると考えたほうが良い

辞書のリストから

data2 = [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]
pd.DataFrame(data2)
a b c
0 1 2 NaN
1 5 10 20.0
pd.DataFrame(data2, index=['first', 'second'])
a b c
first 1 2 NaN
second 5 10 20.0
pd.DataFrame(data2, columns=['a', 'b'])
a b
0 1 2
1 5 10

タプルの辞書から作る

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

tuple_dict = {('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}}
tuple_dict
{('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}}
tuple_dict[('a', 'b')]
{('A', 'B'): 1, ('A', 'C'): 2}
pd.DataFrame(tuple_dict)
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

なんだこれは?

tuple_dict2={}
shinyoko={}
shinyoko[('(着)東京','運賃')]=500
shinyoko[('(着)東京','指定席')]=2460
shinyoko[('(着)東京','自由席')]=860
shinyoko[('(着)品川','運賃')]=170
shinyoko[('(着)品川','指定席')]=2460
shinyoko[('(着)品川','自由席')]=860
shinyoko2={}
shinyoko2[('(着)東京','運賃')]=500*2
shinyoko2[('(着)東京','指定席')]=2460*2
shinyoko2[('(着)東京','自由席')]=860*2
shinyoko2[('(着)品川','運賃')]=170*2
shinyoko2[('(着)品川','指定席')]=2460*2
shinyoko2[('(着)品川','自由席')]=860*2
shinagawa={}
shinagawa[('(着)東京','運賃')]=170
shinagawa[('(着)東京','指定席')]=2460
shinagawa[('(着)東京','自由席')]=860
shinagawa[('(着)新横浜','運賃')]= 410
shinagawa[('(着)新横浜','指定席')]=2460
shinagawa[('(着)新横浜','自由席')]=860
shinagawa2={}
shinagawa2[('(着)東京','運賃')]=170*2
shinagawa2[('(着)東京','指定席')]=2460*2
shinagawa2[('(着)東京','自由席')]=860*2
shinagawa2[('(着)新横浜','運賃')]= 410*2
shinagawa2[('(着)新横浜','指定席')]=2460*2
shinagawa2[('(着)新横浜','自由席')]=860*2
tokyo={}
tokyo[('(着)品川','運賃')]= 170
tokyo[('(着)品川','指定席')]= 2460
tokyo[('(着)品川','自由席')]= 860
tokyo[('(着)新横浜','運賃')]= 500
tokyo[('(着)新横浜','指定席')]= 2460
tokyo[('(着)新横浜','自由席')]= 860
tokyo2={}
tokyo2[('(着)品川','運賃')]= 170*2
tokyo2[('(着)品川','指定席')]= 2460*2
tokyo2[('(着)品川','自由席')]= 860*2
tokyo2[('(着)新横浜','運賃')]= 500*2
tokyo2[('(着)新横浜','指定席')]= 2460*2
tokyo2[('(着)新横浜','自由席')]= 860*2

tuple_dict2[('(発)東京','片道')] = tokyo
tuple_dict2[('(発)東京','往復')] = tokyo2
tuple_dict2[('(発)品川','片道')] = shinagawa
tuple_dict2[('(発)品川','往復')] = shinagawa2
tuple_dict2[('(発)新横浜','片道')] = shinyoko
tuple_dict2[('(発)新横浜','往復')] = shinyoko2

pd.DataFrame(tuple_dict2)
(発)東京 (発)品川 (発)新横浜
片道 往復 片道 往復 片道 往復
(着)品川 指定席 2460.0 4920.0 NaN NaN 2460.0 4920.0
自由席 860.0 1720.0 NaN NaN 860.0 1720.0
運賃 170.0 340.0 NaN NaN 170.0 340.0
(着)新横浜 指定席 2460.0 4920.0 2460.0 4920.0 NaN NaN
自由席 860.0 1720.0 860.0 1720.0 NaN NaN
運賃 500.0 1000.0 410.0 820.0 NaN NaN
(着)東京 指定席 NaN NaN 2460.0 4920.0 2460.0 4920.0
自由席 NaN NaN 860.0 1720.0 860.0 1720.0
運賃 NaN NaN 170.0 340.0 500.0 1000.0

こういう、ことか?MultiIndexedとは、indexやcolumnsをグループ化した状態みたいな

Seriesから作る

結果は、入力Seriesと同じインデックスを持ち、その名前がSeriesの元の名前である1つの列を持つDataFrameになります(他の列名が指定されていない場合のみ)。 欠損データを含むDataFrameを構築するには、欠損値を表すためにnp.nanを使用します。 あるいは、numpy.MaskedArrayをDataFrameコンストラクターへのデータ引数として渡すと、マスクされたエントリは存在しないと見なされます。

s = pd.Series([0,3,6,9,12,15],index=['A','B','C','D','E','F'],name ='example')
s['D']=np.nan
s
A     0.0
B     3.0
C     6.0
D     NaN
E    12.0
F    15.0
Name: example, dtype: float64
pd.DataFrame(s)
example
A 0.0
B 3.0
C 6.0
D NaN
E 12.0
F 15.0
s = pd.Series([0,3,6,9,12,15],index=['A','B','C','D','E','F'],name ='example')
mask_s =[False] * len(s)
mask_s[3] = True
mask_s
[False, False, False, True, False, False]
masked_s=np.ma.masked_array(s , mask=mask_s)
masked_s
masked_array(data=[0, 3, 6, --, 12, 15],
             mask=[False, False, False,  True, False, False],
       fill_value=999999,
            dtype=int64)
type(masked_s)
numpy.ma.core.MaskedArray
pd.DataFrame(masked_s)
0
0 0.0
1 3.0
2 6.0
3 NaN
4 12.0
5 15.0

代替コンストラクタで作る

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

dic = dict([('A', [1, 2, 3]), ('B', [4, 5, 6])])
dic
{'A': [1, 2, 3], 'B': [4, 5, 6]}
pd.DataFrame.from_dict(dic)
A B
0 1 4
1 2 5
2 3 6
pd.DataFrame.from_dict(dic,orient='columns')
A B
0 1 4
1 2 5
2 3 6
pd.DataFrame.from_dict(dic,orient='index')
0 1 2
A 1 2 3
B 4 5 6
pd.DataFrame.from_dict(dic,orient='index',columns=['one', 'two', 'three'])
one two three
A 1 2 3
B 4 5 6

辞書をもとにしたときに、keyとvalueがindex,column列になるがorientを使って逆にすることも可能

DataFrame.from_records

DataFrame.from_recordsタプルのリストまたは構造化dtypeを持つndarrayを取ります。DataFrame結果のDataFrameインデックスが構造化dtypeの特定のフィールドになる場合があることを除けば、通常のコンストラクタと同様に機能します。

data = np.zeros((2, ), dtype=[('A', 'i4'), ('B', 'f4'), ('C', 'U10')])
data[:] = [(1, 2., 'Hello'), (2, 3., "World")]
data
array([(1, 2., 'Hello'), (2, 3., 'World')],
      dtype=[('A', '<i4'), ('B', '<f4'), ('C', '<U10')])
df = pd.DataFrame.from_records(data, index='C')
df
A B
C
Hello 1 2.0
World 2 3.0
df.index
Index(['Hello', 'World'], dtype='object', name='C')
df.index.dtype
dtype('O')

列選択、追加、削除

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

df = pd.DataFrame([[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12]],
                  columns=['A', 'B', 'C'],index=['one','two','three','four'])
df
A B C
one 1 2 3
two 4 5 6
three 7 8 9
four 10 11 12
df['A']
one       1
two       4
three     7
four     10
Name: A, dtype: int64
df['C'] = df['A'] * df['B']
df
A B C
one 1 2 2
two 4 5 20
three 7 8 56
four 10 11 110
df['flag'] = df['A'] > 2
df
A B C flag
one 1 2 2 False
two 4 5 20 True
three 7 8 56 True
four 10 11 110 True

列は辞書を使って削除またはポップすることができます。

del df['B']
c = df.pop('C')
c
one        2
two       20
three     56
four     110
Name: C, dtype: int64
type(c)
pandas.core.series.Series
df
A flag
one 1 False
two 4 True
three 7 True
four 10 True

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

df['foo'] = 'bar'
df
A flag foo
one 1 False bar
two 4 True bar
three 7 True bar
four 10 True bar

DataFrameと同じインデックスを持たないSeriesを挿入すると、DataFrameのインデックスに準拠します。

df['A_trunc'] = df['A'][:2]
df
A flag foo A_trunc
one 1 False bar 1.0
two 4 True bar 4.0
three 7 True bar NaN
four 10 True bar NaN

生のndarrayを挿入できますが、それらの長さはDataFrameのインデックスの長さと一致しなければなりません。 デフォルトでは、列は最後に挿入されます。このinsert関数は列の特定の位置に挿入するのに利用できます。

A列の内容をA列の後ろに'bar'列として挿入

df.insert(1, 'bar', df['A'])
df
A bar flag foo A_trunc
one 1 1 False bar 1.0
two 4 4 True bar 4.0
three 7 7 True bar NaN
four 10 10 True bar NaN
del df['bar']
df
A flag foo A_trunc
one 1 False bar 1.0
two 4 True bar 4.0
three 7 True bar NaN
four 10 True bar NaN
df.columns
Index(['A', 'flag', 'foo', 'A_trunc'], dtype='object')
list(df.columns).index('A')
0
list(df.columns).index('flag')
1
list(df.columns).index('foo')
2
df.insert(list(df.columns).index('foo'), '前','左')
df.insert(list(df.columns).index('foo')+1, '後','右')
df
A flag foo A_trunc
one 1 False bar 1.0
two 4 True bar 4.0
three 7 True bar NaN
four 10 True bar NaN

Matplotlib 3Dプロット、meshgrid とは?

3Dプロット(mplot3d) 、 meshgrid とは?

 始め何やっているのかわからなかったので、どうしてそうなっているのか羅列していきます

f:id:chiyoh:20190512170509p:plain

こんな感じのプロットはどのように描かれているのか?

 次の式に従って描かれたのが上の図です

Z = \frac{4\pi}{\pi+R}cos(\frac{1}{2}\sqrt{X^ 2 + Y^ 2})

 では、上から眺めたらどうなるでしょうか?

f:id:chiyoh:20190512170811p:plain

 Z軸が少し傾いていますね

f:id:chiyoh:20190512170850p:plain

 XYプロットで、描かせるとこんな感じです。どういうことでしょうか?

 グラフを手で書くときには、ある点のに対して座標に沿ってポイントを打っていき、その間を補完する意味で繋げます。

f:id:chiyoh:20190512172109p:plain

 ドットだけ打った図はこんな感じになります。これを上から見てY軸に沿ってドットをつないでいき。次にX軸に沿ってドットをつないでいきます。
 これを横から見るとZX座標、ZY座標

f:id:chiyoh:20190512173043p:plain f:id:chiyoh:20190512173051p:plain

こんな感じになります。式に対してYの値を振った図とXの値を振った図になります。つまり、パラメータが3つあっても、1つを固定すれば点と点をつなぐことができることを意味します。

mashgridについて

 次に、実際に3Dプロットするときに何が必要になるか。すぐ、頭に浮かぶのはZの値がわかればいい。単純にそうでしょうか?  少し単純化して5x5の範囲で線を引く場合を考えていきます

f:id:chiyoh:20190512175131p:plain

 Z=X+Y
の式からZの値は決まりますが、これをplot(X,Y,Z)とやってもプロットしてくれません(いまのところ)。なぜでしょうか?どういう風に点と点をつないでいくかがわからないからです。逆に言うと、線のつなぎ方はいろいろとあるのです。これから行う線の引き方は、z(x1,y1)→z(x2,y2)にせんを引く3Dプロットです。これに対して、numpyライブラリは便利な関数を持っています。meshgridです。

x = [0 1 2 3 4]
y = [0 1 2 3 4] 

5x5の範囲なのでx,yを用意します

X, Y = np.meshgrid(x, y)

実行すると

f:id:chiyoh:20190512180742p:plain

 このような配列を作ってくれます。はじめなんじゃこれは?と思っていたのですが、こういうことです

f:id:chiyoh:20190512181301p:plain f:id:chiyoh:20190512181307p:plain

 X,Y,Zすべての要素が決まったことになり、これに対してZXグラフとZYグラフを書く準備ができたということです。
 1~5までは、Y軸を固定にしてX軸に沿って線を引いていきます。次に、6~10は、X軸を固定してY軸に沿って線を引いていきます。
 この要領でプロットするのがplot_wireframeになります。

実際のソース

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d


# 配列の要素数
n = 5
x = np.linspace(0, 4, n)
y = np.linspace(0, 4, n)
print("x =",x)
print("y =",y,"\n")

# 5×5の格子点を作成
X, Y = np.meshgrid(x, y)
R = np.sqrt(X**2 + Y**2) #原点からの長さ
Z = 4*np.pi/(np.pi+R)*np.cos(np.pi*R/2)

np.set_printoptions(precision=3, suppress=True)
print("Xサイズ ",X.shape)
print("X =",X,"\n")
print("Yサイズ ",Y.shape)
print("Y =",Y,"\n")
print("Zサイズ ",Y.shape)
print("Z =",Z,"\n")
print("X=3")
print("X[:,3] =",X[:,3])
print("Y[:,3] =",Y[:,3])
print("Z[:,3] =",Z[:,3])
print("\n")
print("Y=3")
print("X[3,:] =",X[3,:])
print("Y[3,:] =",Y[3,:])
print("Z[3,:] =",Z[3,:])
x = [0. 1. 2. 3. 4.]
y = [0. 1. 2. 3. 4.] 

Xサイズ  (5, 5)
X = [[0. 1. 2. 3. 4.]
 [0. 1. 2. 3. 4.]
 [0. 1. 2. 3. 4.]
 [0. 1. 2. 3. 4.]
 [0. 1. 2. 3. 4.]] 

Yサイズ  (5, 5)
Y = [[0. 0. 0. 0. 0.]
 [1. 1. 1. 1. 1.]
 [2. 2. 2. 2. 2.]
 [3. 3. 3. 3. 3.]
 [4. 4. 4. 4. 4.]] 

Zサイズ  (5, 5)
Z = [[ 4.     0.    -2.444 -0.     1.76 ]
 [ 0.    -1.671 -2.178  0.503  1.698]
 [-2.444 -2.178 -0.56   1.516  1.217]
 [-0.     0.503  1.516  1.58   0.   ]
 [ 1.76   1.698  1.217  0.    -1.226]] 

X=3
X[:,3] = [3. 3. 3. 3. 3.]
Y[:,3] = [0. 1. 2. 3. 4.]
Z[:,3] = [-0.     0.503  1.516  1.58   0.   ]


Y=3
X[3,:] = [0. 1. 2. 3. 4.]
Y[3,:] = [3. 3. 3. 3. 3.]
Z[3,:] = [-0.     0.503  1.516  1.58   0.   ]

 各要素毎に、5x5の配列で用意できてます。またNumpyですので、Z=f(X,Y)的な演算もそのままできて便利です。X=3の時は、X,Y,Zを使ってこの順で線を引きます。Y=3の時は、X,Y,Zを使ってこの順で線を引きます。

実際のプロット

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np

# 配列の要素数
n = 5
x = np.linspace(0, 4, n)
y = np.linspace(0, 4, n)
print("x =",x)
print("y =",y,"\n")

# 5×5の格子点を作成
X, Y = np.meshgrid(x, y)
R = np.sqrt(X**2 + Y**2) #原点からの長さ
Z = 4*np.pi/(np.pi+R)*np.cos(np.pi*R/2)

fig = plt.figure()
ax = fig.gca(projection='3d')

wire = ax.plot_wireframe(X, Y, Z)

ax.set_xlabel("X", size = 15)
ax.set_ylabel("Y", size = 15)
ax.set_zlabel("Z", size = 15)

ax.set_zlim(-5, 5)
ax.zaxis.set_major_locator(LinearLocator(6))

plt.show()

f:id:chiyoh:20190512182730p:plain

 格子が5x5なので少しガタガタしてますね。

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')

X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2) #原点からの長さ
Z = 4*np.pi/(np.pi+R)*np.cos(np.pi*R/2)

wire = ax.plot_wireframe(X, Y, Z)

ax.set_xlabel("X", size = 15)
ax.set_ylabel("Y", size = 15)
ax.set_zlabel("Z", size = 15)

ax.set_zlim(-5, 5)
ax.zaxis.set_major_locator(LinearLocator(6))

plt.show()

f:id:chiyoh:20190512170509p:plain

 というわけで、3Dプロットはなにをやっているのか?mashgridは、何を作っているのか。また、その役割について話しました。