matplotlib pcolor

matplotlib の pcolor

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
#from matplotlib.colors import LogNorm

#Z = np.random.rand(6, 10)
Z = 100*np.random.randn(64,256)
Z = np.where(Z < -256, -256, Z)
Z = np.where(Z > 255, 255, Z)

fig, (ax0, ax1) = plt.subplots(2, 1)

fig.dpi = 200 # 200dpi

c = ax0.pcolor(Z)
ax0.set_aspect('equal')
ax0.set_title('default: no edges')
fig.colorbar(c, ax=ax0)

c = ax1.pcolor(Z, edgecolors='k', linewidths=0.5)
ax1.set_title('thick edges')
ax1.set_aspect('equal')
fig.colorbar(c, ax=ax1)

fig.tight_layout()
plt.show()

f:id:chiyoh:20190624232805p:plain

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
#from matplotlib.colors import LogNorm

#Z = np.random.rand(6, 10)
Z = 100*np.random.randn(64,256)
Z = np.where(Z < -256, -256, Z)
Z = np.where(Z > 255, 255, Z)

fig, ax = plt.subplots(2, 2)
fig.dpi = 200 # 200dpi

ax2 = ax[1][0]
c = ax2.pcolor(Z)
#ax2.set_aspect('equal')
#ax2.set_title('default: no edges')

ax1 = ax[0][1]
fig.colorbar(c, ax=ax1)

ax0 = ax[0][0]
c = ax0.bar(range(Z.shape[1]),np.sum(Z,axis=0))
ax0.set_xlim(ax2.get_xlim())

ax3 = ax[1][1]
c = ax3.barh(range(Z.shape[0]),np.sum(Z,axis=1))
ax3.set_ylim(ax2.get_ylim())



fig.tight_layout()
plt.show()

f:id:chiyoh:20190624232828p:plain

matplotlib の hist

データをプロットして、ヒストを作って、確認する

いつものように、乱数でデータを作って、プロットとヒストとその結果を表示してみる

import matplotlib.pyplot as plt
import numpy as np

def print_bins(n,bins):

    tmp = 'hist data:\n'

    if len(n) != len(bins)-1:
        return "Error not equal n,(bins-1).\n"
    elif len(bins) < 1:
        return "Error bins\n"
    elif len(bins) == 2:
        return tmp + '[{},{}] {}\n'.format(bins[0],bins[1],n[0])

    for st,ed,ct in zip(bins[0:-2],bins[1:],n):
        tmp += '[{:>8.3f},{:>8.3f}) {:>6.0f}\n'.format(st,ed,ct)
    tmp += '[{:>8.3f},{:>8.3f}] {:>6.0f}\n'.format(bins[-2],bins[-1],n[-1])

    return tmp

def print_info(dat):
    tmp = 'info:\n'
    tmp += 'count = {}\n'.format(len(dat))
    tmp += 'min   = {}\n'.format(np.amin(dat))
    tmp += 'max   = {}\n'.format(np.amax(dat))
    tmp += 'mean  = {}\n'.format(np.mean(dat))
    tmp += 'std   = {}\n'.format(np.std(dat))
    tmp += 'var   = {}\n'.format(np.var(dat))
    return tmp

今回は、事前にやりたいことを確認していたので

cnt = 100
dat = np.random.normal(1, 2, cnt)

fig = plt.figure()
fig.dpi = 200 # 200dpi

#fig, ax_lst = plt.subplots(2, 2)
ax1=fig.add_subplot(2,2,1) # row=1,col=2,index=1
ax2=fig.add_subplot(2,2,2) # row=1,col=2,index=2
ax3=fig.add_subplot(2,2,3) # row=1,col=2,index=3
ax4=fig.add_subplot(2,2,4) # row=1,col=2,index=4

x = np.arange(0, cnt)

ax1.set_title("randn({})".format(cnt))
ax1.plot(x,dat)

ax2.set_title("hist")
pHist = ax2.hist(dat,bins=10,orientation='horizontal')

dat_info = print_info(dat)
ax3.tick_params(labelbottom=False,labeltop=False,labelleft=False,labelright=False,
                bottom=False, top=False, left=False, right=False)
ax3.text(0.1,1-0.1,dat_info,size=6,family='monospace',horizontalalignment='left', verticalalignment='top',transform=ax3.transAxes)

hist_text = print_bins(pHist[0],pHist[1])
ax4.tick_params(labelbottom=False,labeltop=False,labelleft=False,labelright=False,
                bottom=False, top=False, left=False, right=False)
ax4.text(0.1,1-0.1,hist_text,size=6,family='monospace',horizontalalignment='left', verticalalignment='top',transform=ax4.transAxes)

plt.show()

f:id:chiyoh:20190624232517p:plain

【解説】左上データをプロット、右上データをヒストプロット、右下ヒストのデータ、左下データの平均値とかいろいろ




    
  

xlsxwriter.utility 詳細

xlsxwriter.utility

Python ライブラリ xlsxwriter の xlsxwriter.utility は、xlsxwriter内部で使われるヘルパーメソッドですが便利なのでユーザーにも開放されています。

f:id:chiyoh:20190615212135p:plain

前書き

いつものように、調べた結果を羅列していきます。

準備(xlswritewを使ってみる)

準備としてxlsxwriterでインスタンスを作ります。

import xlsxwriter
import xlsxwriter.utility
# VBAみたいですね。はじめにWorkbookのインスタンスを作ります
workbook = xlsxwriter.Workbook('example01.xlsx')
# workbookにworksheetを追加します
worksheet = workbook.add_worksheet()
# セルデータを配列とtupleで作ります
data = (
    ['経路', '運賃','特急料金'],
    ['大阪', 8960, 0 ],
    ['新大阪', 0, 5700],
    ['東京', 0,0],
    ['舞浜/リゾートゲートウェイ', 260,0],
    ['東京ディズニーシー',    0,0],
    ['小計', '=SUM(B2:B6)' , '=SUM(C2:C6)'],
    ['合計',  '' ,'=B7+C7'],
)
# 配列を使うのでindexはゼロから始めます
row = 0
col = 0
# ワークシートにwriteメソッドでrow,colを指定して書き込んでいるだけです。
for item, cost1,cost2 in (data):
    worksheet.write(row, col,     item)
    worksheet.write(row, col + 1, cost1)
    worksheet.write(row, col + 2, cost2)
    row += 1
# 作ったワークブックを閉じると保存されるみたいです。
workbook.close()

f:id:chiyoh:20190615212255p:plain

うーん。簡単でしょ?!

xlsxwriter.utility

xlsxwriter.utilityで定義されているメソッド一覧です

xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False)

row(行)とcolumn(列)のセル参照をA1表示方式の文字列に変換します。rowとcolは0スタートの整数。row_absとcol_abs は絶対値表記で'$'が付きます。

引数

  • row: int セル行(first rowは、0)
  • col: int セル列(first columnは、0)
  • row_abs: boolean セル行を絶対値にする。ディフォルトはFalse
    • True セル行を絶対値にする
    • False セル行を相対値にする
  • col_abs: boolean セル列を絶対値にする。ディフォルトはFalse
    • True セル列を絶対値にする
    • False セル列を相対値にする

戻り値

  • str A1表示方式の文字列
xlsxwriter.utility.xl_rowcol_to_cell(0,0)
'A1'
xlsxwriter.utility.xl_rowcol_to_cell(1,20,0,1)
'$U2'

【解説】A1表示方式は、表を見ながら操作するのには便利であるがプログラムする側からするとカラムを5つ奥に移動するとcolumn名は何になる?forでループはできない?などと不便である。VBAではoffset等を使ってしのいでいるが。。。なのでセルに数式で埋め込むとき等に便利そうなメソッドである。

xl_rowcol_to_cell_fast(row, col)

xl_rowcol_to_cell関数の最適化バージョン 内部でのみ使用されています。

引数

  • row: int セル行(first rowは、0)
  • col: int セル列(first colは、0)

戻り値

  • str A1表示方式の文字列

【解説】絶対値表記等は、EXCEL上の操作の際に意味があることでバックグランドのプログラムではrow,col共に配列番号なので必要ないので余計なところを端折って高速化したメソッドである。

xl_col_to_name(col, col_abs=False)

列番号を列セル参照を文字列に変換します。col_abs は絶対値表記で'$'が付きます。

引数

  • col: int セル列(first columnは、0)
  • col_abs: boolean セル列を絶対値にする。ディフォルトはFalse
    • True セル列を絶対値にする
    • False セル列を相対値にする

戻り値

  • str A1表記方式の列文字列
xlsxwriter.utility.xl_col_to_name(0,1)
'$A'
xlsxwriter.utility.xl_col_to_name(30)
'AE'

【解説】早い話列番号を列アルファベット文字列に変換。行に関してはゼロスタートなのでA1表示方式にするには+1すれば終わる。

xl_cell_to_rowcol(cell_str)

A1表記方式文字列を、row,col数値行列に変換する。

引数

  • cell_str str A1表記方式文字列($A$1等の絶対値表記も可)

戻り値

  • row: int セル行(first rowは、0)
  • col: int セル列(first columnは、0)
xlsxwriter.utility.xl_cell_to_rowcol('ABC$1000')
(999, 730)

【解説】xl_rowcol_to_cell_fastの逆変換

xl_cell_to_rowcol_abs(cell_str)

A1表記方式文字列を、row,col数値行列に変換する。絶対値表記も処理する

引数

  • cell_str str A1表記方式文字列($A$1等の絶対値表記も可)

戻り値

  • row: int セル行(first rowは、0)
  • col: int セル列(first columnは、0)
  • row_abs: boolean セル行を絶対値にする。ディフォルトはFalse
    • True セル行を絶対値にする
    • False セル行を相対値にする
  • col_abs: boolean セル列を絶対値にする。ディフォルトはFalse
    • True セル列を絶対値にする
    • False セル列を相対値にする
xlsxwriter.utility.xl_cell_to_rowcol_abs('ABC$1000')
(999, 730, True, False)

【解説】xl_rowcol_to_cellの逆変換

xl_range(first_row, first_col, last_row, last_col)

行と列のセル参照をA1:B2形式範囲文字列に変換します。

引数

  • first_row: int 最初のセル行(first rowは、0)
  • first_col: int 最初のセル列(first columnは、0)
  • last_row: int 最後のセル行(first rowは、0)
  • last_col: int 最後のセル列(first rowは、0)

戻り値

  • str A1:B2形式範囲文字列
xlsxwriter.utility.xl_range(10,20,30,40)
'U11:AO31'

【解説】:でxl_rowcol_to_cell_fastを使って挟んだ感じ

xl_range_abs(first_row, first_col, last_row, last_col)

行と列のセル参照を$A$1:$B$2形式絶対範囲文字列に変換します。

引数

  • first_row: int 最初のセル行(first rowは、0)
  • first_col: int 最初のセル列(first rowは、0)
  • last_row: int 最後のセル行(first rowは、0)
  • last_col: int 最後のセル列(first rowは、0)

戻り値

  • str $A$1:$B$2形式範囲文字列
xlsxwriter.utility.xl_range_abs(40,30,20,10)
'$AE$41:$K$21'

【解説】xl_rangeの絶対値付きの範囲文字列変換であるが、相対値と絶対値の混合ができない

xl_range_formula(sheetname, first_row, first_col, last_row, last_col)

ワークシート名と行列番号をSheet1!A1:B2範囲表示方式文字列に変換

引数

  • sheetname: str ワークシート名
  • first_row: int 最初のセル行(first rowは、0)
  • first_col: int 最初のセル列(first columnは、0)
  • last_row: int 最後のセル行(first rowは、0)
  • last_col: int 最後のセル列(first columnは、0)

戻り値

  • str Sheet1!A1:B2範囲表示方式文字列に変換
xlsxwriter.utility.xl_range_formula('sheet1',4,3,7,101)
'sheet1!$D$5:$CX$8'

【解説】quote_sheetnameとxl_range_absを足した感じ

quote_sheetname(sheetname)

ワークシート名にスペースまたは特殊文字が含まれている場合は、 その名前を引用符付きの名前に変換します。

引数

  • sheetname: str ワークシートの名前

戻り値

  • str 引用符付きのワークシート

【解説】引数が数値文字列以外は、文字列を引用符付きにする。例外処理もやる

# シート名は引用符を付ける
st_name = xlsxwriter.utility.quote_sheetname('結果')
st_name
'結果'
# シート名にスペースが入っていようとなかろうと関係ない
print(xlsxwriter.utility.quote_sheetname('結果 1'))
'結果 1'
# 2重にかけても無視される
print(xlsxwriter.utility.quote_sheetname(st_name))
結果
# VBAとかでもシート名を数値(バーバリアン型)で表すので数値で戻す
print(xlsxwriter.utility.quote_sheetname("5"))
5

xl_color(color)

XlsxWriterのcolorメソッドで一緒に使用して変換してます。RGB形式の文字列に変換します。これらの色は以前のバージョンのExcelとの下位互換性。

引数

  • color: str 色名 または、 RGB形式文字列

戻り値

  • str ARGB形式

注意

  • 'AARRGGBB'フォーマットで返される
  • 対応色名
    • 'black','blue','brown','cyan','gray','green','lime','magenta',
    • 'navy','orange','pink','purple','red','silver','white','yellow'
#色名
xlsxwriter.utility.xl_color('silver')
'FFC0C0C0'
#色コード
xlsxwriter.utility.xl_color('#00FF00')
'FF00FF00'
#アルファ付き色コード
xlsxwriter.utility.xl_color('#80FFFF00')
'FF80FFFF00'

get_rgb_color(color)

色名をRGB形式の文字列に変換します。

引数

  • color: str 色名

戻り値

  • str RGB形式

注意

  • 'RRGGBB'フォーマットで返される
#色名
xlsxwriter.utility.get_rgb_color('silver')
'C0C0C0'

【解説】xl_color(color)のアルファ無版

get_sparkline_style(style_id)

スパークラインのスタイルを収得します(add_sparklineで使用している)

引数

  • style_id: int スタイル番号

戻り値

  • dict スタイル情報

注意

  • サポートされているのは0~36
xlsxwriter.utility.get_sparkline_style(0)
{'series': {'theme': '4', 'tint': '-0.499984740745262'},
 'negative': {'theme': '5'},
 'markers': {'theme': '4', 'tint': '-0.499984740745262'},
 'first': {'theme': '4', 'tint': '0.39997558519241921'},
 'last': {'theme': '4', 'tint': '0.39997558519241921'},
 'high': {'theme': '4'},
 'low': {'theme': '4'}}
xlsxwriter.utility.get_sparkline_style(36)
{'series': {'theme': '1'},
 'negative': {'theme': '9'},
 'markers': {'theme': '8'},
 'first': {'theme': '4'},
 'last': {'theme': '5'},
 'high': {'theme': '6'},
 'low': {'theme': '7'}}

supported_datetime(dt)

xlsxwrite(excel)のChartやWorksheetでサポートされている日時オブジェクトかどうかを判別します。

引数

  • dt: 検査するオブジェクト

戻り値

  • boolean
    • True サポートする
    • False サポートしない

注意

  • サポートされているのはdatetimeのdatetime,date,time,timedeltaです
from datetime import datetime
xlsxwriter.utility.supported_datetime(datetime.today())
True
print(datetime.today())
2019-06-15 21:18:11.820430
xlsxwriter.utility.supported_datetime("2019-06-15 10:16:46.687568")
False
d = datetime(2019, 2, 3)
xlsxwriter.utility.supported_datetime(d)
True

remove_datetime_timezone(dt_obj, remove_timezone)

Excelでは日時のタイムゾーンはサポートされていないため削除する関数。引数で指定した日時オブジェクトからtzinfoを削除します。

引数

  • dt_obj: datetimeクラスのオブジェクト
  • remove_timezone: 削除するタイムゾーン

戻り値

  • datetimeクラスのオブジェクト
from datetime import datetime, timedelta, timezone
JST = timezone(timedelta(hours=+9), 'JST')
jtime = datetime.now(JST)
jtime
datetime.datetime(2019, 6, 15, 21, 18, 12, 452426, tzinfo=datetime.timezone(datetime.timedelta(seconds=32400), 'JST'))
xlsxwriter.utility.supported_datetime(jtime)
True
t = xlsxwriter.utility.remove_datetime_timezone(jtime,'JST')
t
datetime.datetime(2019, 6, 15, 21, 18, 12, 452426)
xlsxwriter.utility.supported_datetime(t)
True

datetime_to_excel_datetime(dt_obj, date_1904, remove_timezone)

日時オブジェクトをExcelのシリアルの日時に変換します。 数値の整数部にはエポック以降の日数が格納され、小数部にはその日の割合が格納されます。

引数

  • dt_obj: datetimeクラスのオブジェクト
  • date_1904: boolean エポックを'1904-01-01'にするか'1899-12-31'にするか選択。
    • True '1904-01-01'
    • False '1899-12-31'
  • remove_timezone: boolean remove_datetime_timezoneを使ってタイムゾーンを削除するかどうか
    • True 削除する
    • False 削除しない

戻り値

xlsxwriter.utility.datetime_to_excel_datetime(jtime,True,True)
42169.8876441253
xlsxwriter.utility.datetime_to_excel_datetime(jtime,False,True)
43631.8876441253

Pandas 集中講座 その6 時系列、カテゴリ、プロット、データ入出力

Pandasライブラリを覚えたい!公式「10 Minutes to pandas」をモチーフに使って実際に動作させ学習する。第6回 時系列、カテゴリ、プロット、データ入出力

Pandas 集中講座(6)

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」を実際にやってみる (6/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」を実際にやってみる

9.時系列

Pandasは、周波数変換中にリサンプリング操作を実行するための単純で強力で効率的な機能を持っています(例えば、2番目のデータを5分ごとのデータに変換する)。これは金融アプリケーションでは非常に一般的ですが、これに限定されません。時系列のセクションを参照してください。

rng = pd.date_range('2019/1/1', periods=1000, freq='s')
rng[:10]
DatetimeIndex(['2019-01-01 00:00:00', '2019-01-01 00:00:01',
               '2019-01-01 00:00:02', '2019-01-01 00:00:03',
               '2019-01-01 00:00:04', '2019-01-01 00:00:05',
               '2019-01-01 00:00:06', '2019-01-01 00:00:07',
               '2019-01-01 00:00:08', '2019-01-01 00:00:09'],
              dtype='datetime64[ns]', freq='S')

【解説】pd.date_rangeは、'2019/1/1'から 100回繰り返し, 周期は秒でということでしょうか?freq='s'は、小文字のsでもいけるみたいです。内部処理では'S'を使っております。なんでしょうね。これ、中二病でしょうか?もう、21世紀結構たつのにSI単位系での授業でやらないのでしょうか?やっぱり、(mS)と書いたほうが格好いい!という世代がいるのでしょうか。困ったものです。

ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
ts.head()
2019-01-01 00:00:00    355
2019-01-01 00:00:01    100
2019-01-01 00:00:02    103
2019-01-01 00:00:03    258
2019-01-01 00:00:04    193
Freq: S, dtype: int32
ts.tail()
2019-01-01 00:16:35    447
2019-01-01 00:16:36    410
2019-01-01 00:16:37    362
2019-01-01 00:16:38    248
2019-01-01 00:16:39    279
Freq: S, dtype: int32
ts.resample('3Min')
DatetimeIndexResampler [freq=<3 * Minutes>, axis=0, closed=left, label=left, convention=start, base=0]
ts.resample('3Min').sum()
2019-01-01 00:00:00    45914
2019-01-01 00:03:00    43718
2019-01-01 00:06:00    45345
2019-01-01 00:09:00    47489
2019-01-01 00:12:00    44633
2019-01-01 00:15:00    24273
Freq: 3T, dtype: int32

【解説】Freq: 3Tは、3分のことです。周期的に集計ができるみたいです。

タイムゾーン表示:

rng = pd.date_range('3/6/2017 00:00', periods=5, freq='D')
ts = pd.Series(np.random.randn(len(rng)), rng)
ts
2017-03-06    1.022949
2017-03-07   -0.556746
2017-03-08    1.040104
2017-03-09    0.901689
2017-03-10   -0.511703
Freq: D, dtype: float64
type(rng)
pandas.core.indexes.datetimes.DatetimeIndex
ts_utc = ts.tz_localize('UTC')
ts_utc
2017-03-06 00:00:00+00:00    1.022949
2017-03-07 00:00:00+00:00   -0.556746
2017-03-08 00:00:00+00:00    1.040104
2017-03-09 00:00:00+00:00    0.901689
2017-03-10 00:00:00+00:00   -0.511703
Freq: D, dtype: float64
ts_jst = ts.tz_localize('Asia/Tokyo')
ts_jst
2017-03-06 00:00:00+09:00    1.022949
2017-03-07 00:00:00+09:00   -0.556746
2017-03-08 00:00:00+09:00    1.040104
2017-03-09 00:00:00+09:00    0.901689
2017-03-10 00:00:00+09:00   -0.511703
Freq: D, dtype: float64

【解説】DatetimeIndexのindexを使っているので単なる文字列ではなく、時系列として内部処理されています。よって、ローカル時間に変換みたいなことも可能になります。上記の例ではcoordinated universal timeなのになぜかUTCの略字の協定世界時の事です。日本の場合ts.tz_localize('Asia/Tokyo')とかですかね。+0900にはなります。

別のタイムゾーンに変換する:

ts_utc.tz_convert('US/Eastern')
2017-03-05 19:00:00-05:00    1.022949
2017-03-06 19:00:00-05:00   -0.556746
2017-03-07 19:00:00-05:00    1.040104
2017-03-08 19:00:00-05:00    0.901689
2017-03-09 19:00:00-05:00   -0.511703
Freq: D, dtype: float64
ts_utc.tz_convert('Asia/Tokyo')
2017-03-06 09:00:00+09:00    1.022949
2017-03-07 09:00:00+09:00   -0.556746
2017-03-08 09:00:00+09:00    1.040104
2017-03-09 09:00:00+09:00    0.901689
2017-03-10 09:00:00+09:00   -0.511703
Freq: D, dtype: float64

【解説】00:00:00+00:00:00から9時間足した09:00:00にして+09:00のoffsetを付ければUTCで00:00:00+00:00時刻で日本では09:00:00+09:00と同じことになります。

タイムスパン表現間の変換:

rng = pd.date_range('1/1/2018', periods=5, freq='M')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts
2018-01-31    0.649878
2018-02-28   -0.316178
2018-03-31    0.601950
2018-04-30   -2.371119
2018-05-31    2.307606
Freq: M, dtype: float64
type(ts.index)
pandas.core.indexes.datetimes.DatetimeIndex
ps = ts.to_period()
ps
2018-01    0.649878
2018-02   -0.316178
2018-03    0.601950
2018-04   -2.371119
2018-05    2.307606
Freq: M, dtype: float64
type(ps.index)
pandas.core.indexes.period.PeriodIndex
ps.to_timestamp()
2018-01-01    0.649878
2018-02-01   -0.316178
2018-03-01    0.601950
2018-04-01   -2.371119
2018-05-01    2.307606
Freq: MS, dtype: float64
ps.to_timestamp(how='end')
2018-01-31 23:59:59.999999999    0.649878
2018-02-28 23:59:59.999999999   -0.316178
2018-03-31 23:59:59.999999999    0.601950
2018-04-30 23:59:59.999999999   -2.371119
2018-05-31 23:59:59.999999999    2.307606
Freq: M, dtype: float64

【解説】変換DatetimeIndexからPeriodIndexに変換。月末集計のデータと何月のデータという感じかな?

ピリオドとタイムスタンプを変換すると、便利な算術関数を使用できます。次の例では、11月に終了する年の四半期頻度を、四半期終了後の月末の午前9時に変換します。

prng = pd.period_range('1990Q1', '2000Q4', freq='Q-NOV')
ts = pd.Series(np.random.randn(len(prng)), prng)
ts.index = (prng.asfreq('M', 'e') + 1).asfreq('H', 's') + 9
ts.head()
1990-03-01 09:00   -0.929966
1990-06-01 09:00   -0.153660
1990-09-01 09:00    1.111212
1990-12-01 09:00   -1.835358
1991-03-01 09:00   -0.440545
Freq: H, dtype: float64

10.カテゴリ

PandasはDataFrameにカテゴリカルデータを含めることができます。完全なドキュメントについては、カテゴリ別紹介とAPIドキュメントを参照してください。

df = pd.DataFrame({"id": [1, 2, 3, 4, 5, 6],"raw_grade": ['a', 'b', 'b', 'a', 'a', 'e']})
df
id raw_grade
0 1 a
1 2 b
2 3 b
3 4 a
4 5 a
5 6 e

生の成績をカテゴリカルデータ型に変換します。

df["grade"] = df["raw_grade"].astype("category")
df["grade"]
0    a
1    b
2    b
3    a
4    a
5    e
Name: grade, dtype: category
Categories (3, object): [a, b, e]

【解説】dtypeをカテゴリに変更

カテゴリの名前をより意味のある名前に変更します(に代入するの Series.cat.categoriesは適切です)。

df["grade"].cat.categories = ["very good", "good", "very bad"]
df
id raw_grade grade
0 1 a very good
1 2 b good
2 3 b good
3 4 a very good
4 5 a very good
5 6 e very bad

カテゴリを並べ替えると同時に不足しているカテゴリを追加します(メソッドはデフォルトで新しいを返します)。Series .catSeries

df["grade"] = df["grade"].cat.set_categories(["very bad",
        "bad", "medium", "good", "very good"])
df["grade"]
0    very good
1         good
2         good
3    very good
4    very good
5     very bad
Name: grade, dtype: category
Categories (5, object): [very bad, bad, medium, good, very good]

ソートは字句順ではなく、カテゴリ内の順番ごとに行われます。

df.sort_values(by="grade")
id raw_grade grade
5 6 e very bad
1 2 b good
2 3 b good
0 1 a very good
3 4 a very good
4 5 a very good

カテゴリ別の列でグループ化すると、空のカテゴリも表示されます。

df.groupby("grade").size()
grade
very bad     1
bad          0
medium       0
good         2
very good    3
dtype: int64

11.プロット

プロットドキュメントを参照してください。

ts = pd.Series(np.random.randn(1000),
index=pd.date_range('1/1/2000', periods=1000))
ts = ts.cumsum()
ts.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x18bbf22e780>

f:id:chiyoh:20190609100940p:plain

df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index,columns=['A', 'B', 'C', 'D'])
df = df.cumsum()

DataFrameでは、このplot()メソッドはすべての列をラベル付きでプロットするのに便利です。

df.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x18bbf4ae630>

f:id:chiyoh:20190609100911p:plain

12.データを入出力する

CSV

csvファイルへの書き込み

df.to_csv('foo.csv')

csvファイルからの読み取り

pd.read_csv('foo.csv')[:10]
Unnamed: 0 A B C D
0 2000-01-01 0.277849 -0.854028 0.997447 0.683385
1 2000-01-02 2.380521 -1.792953 0.961907 -0.503520
2 2000-01-03 2.159351 -1.587149 0.124225 -0.216280
3 2000-01-04 1.919500 0.916933 0.943439 1.193825
4 2000-01-05 2.702664 0.660521 0.066178 2.300615
5 2000-01-06 3.730054 2.200855 -0.658531 3.261455
6 2000-01-07 4.698684 2.556608 -0.882894 4.743601
7 2000-01-08 4.371757 1.637595 0.574554 4.217433
8 2000-01-09 4.557831 0.725914 0.493527 4.998890
9 2000-01-10 5.512622 0.575462 0.350790 4.389341

HDF5

HDFStoreへの読み書き。

HDF5ストアへの書き込み

df.to_hdf('foo.h5', 'df')

HDF5ストアからの読み取り

pd.read_hdf('foo.h5', 'df')[:10]
A B C D
2000-01-01 0.277849 -0.854028 0.997447 0.683385
2000-01-02 2.380521 -1.792953 0.961907 -0.503520
2000-01-03 2.159351 -1.587149 0.124225 -0.216280
2000-01-04 1.919500 0.916933 0.943439 1.193825
2000-01-05 2.702664 0.660521 0.066178 2.300615
2000-01-06 3.730054 2.200855 -0.658531 3.261455
2000-01-07 4.698684 2.556608 -0.882894 4.743601
2000-01-08 4.371757 1.637595 0.574554 4.217433
2000-01-09 4.557831 0.725914 0.493527 4.998890
2000-01-10 5.512622 0.575462 0.350790 4.389341

エクセル

読み取りと書き込みにMSエクセル。

Excelファイルへの書き込み

df.to_excel('foo.xlsx', sheet_name='Sheet1')

Excelファイルからの読み取り

pd.read_excel('foo.xlsx', 'Sheet1', index_col=None, na_values=['NA'])[:10]
Unnamed: 0 A B C D
0 2000-01-01 0.277849 -0.854028 0.997447 0.683385
1 2000-01-02 2.380521 -1.792953 0.961907 -0.503520
2 2000-01-03 2.159351 -1.587149 0.124225 -0.216280
3 2000-01-04 1.919500 0.916933 0.943439 1.193825
4 2000-01-05 2.702664 0.660521 0.066178 2.300615
5 2000-01-06 3.730054 2.200855 -0.658531 3.261455
6 2000-01-07 4.698684 2.556608 -0.882894 4.743601
7 2000-01-08 4.371757 1.637595 0.574554 4.217433
8 2000-01-09 4.557831 0.725914 0.493527 4.998890
9 2000-01-10 5.512622 0.575462 0.350790 4.389341

終わった!

って全然終わってねー。毎日コツコツやっても全然終わらない!ほんとに10分の内容なのか?10日じゃないのか?

chiyoh.hatenablog.com
chiyoh.hatenablog.com
chiyoh.hatenablog.com
chiyoh.hatenablog.com
chiyoh.hatenablog.com
chiyoh.hatenablog.com

Pandas 集中講座 その5 グループ、再構成

Pandasライブラリを覚えたい!公式「10 Minutes to pandas」をモチーフに使って実際に動作させ学習する。第5回 グループ、再構成

Pandas 集中講座(5)

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」を実際にやってみる (5/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」を実際にやってみる

7.グループ化

「グループ化」とは、以下のステップのうちの1つ以上を含むプロセスを指します。

  • いくつかの基準に基づいてデータをグループに分割する
  • 各グループに独立して機能を適用する
  • 組み合わせデータ構造に結果を

グループ化の節を参照してください。

df = pd.DataFrame({'A': ['foo', 'bar', 'foo', 'bar','foo', 'bar', 'foo', 'foo'],
                    'B': ['one', 'one', 'two', 'three','two', 'two', 'one', 'three'],
                    'C': np.random.randn(8),
                    'D': np.random.randn(8)})
df
A B C D
0 foo one 0.447141 0.013312
1 bar one 0.546690 0.039544
2 foo two -0.520826 1.888179
3 bar three 0.788315 -0.085696
4 foo two 0.542692 -1.728545
5 bar two -1.449532 -0.242919
6 foo one 1.238537 -0.221444
7 foo three 0.633757 0.419697

グループ化してsum()から、結果のグループに関数を適用します。

df.groupby('A').sum()
C D
A
bar -0.114527 -0.289071
foo 2.341301 0.371199

【コメント】B列が消えてしまうんですね。

複数の列でグループ化することで階層的なインデックスが形成され、やはりsum関数を適用できます。

df.groupby(['A', 'B']).sum()
C D
A B
bar one 0.546690 0.039544
three 0.788315 -0.085696
two -1.449532 -0.242919
foo one 1.685677 -0.208132
three 0.633757 0.419697
two 0.021867 0.159634

8.再整形

階層化された索引付けと 再整形に関するセクションを見てください。

スタック

tuples = list(zip(*[['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
                    ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]))
tuples
[('bar', 'one'),
 ('bar', 'two'),
 ('baz', 'one'),
 ('baz', 'two'),
 ('foo', 'one'),
 ('foo', 'two'),
 ('qux', 'one'),
 ('qux', 'two')]
index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
index
MultiIndex(levels=[['bar', 'baz', 'foo', 'qux'], ['one', 'two']],
           codes=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
           names=['first', 'second'])

【解説】マルチインデックスのindexを作っております。タプルから組み合わせを作りindex名を付けて用意する

df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B'])
df
A B
first second
bar one 0.681572 0.922410
two -0.273510 -1.867662
baz one -0.072902 0.386001
two -1.570309 -0.933379
foo one -0.054618 0.587165
two 0.411690 0.241520
qux one 0.683476 1.856185
two -0.435505 0.604112

【解説】index8つに対してデータ割り当てDataFrameを作ります。マルチインデックスの役割は他で調べてください。ほかのページでも説明してます。

df2 = df[:4]
df2
A B
first second
bar one 0.681572 0.922410
two -0.273510 -1.867662
baz one -0.072902 0.386001
two -1.570309 -0.933379

【解説】マルチインデックスでも、上から4つをスライスしたデータを取り出せます。

このstack()メソッドは、DataFrameの列のレベルを「圧縮」します。

stacked = df2.stack()
stacked
first  second   
bar    one     A    0.681572
               B    0.922410
       two     A   -0.273510
               B   -1.867662
baz    one     A   -0.072902
               B    0.386001
       two     A   -1.570309
               B   -0.933379
dtype: float64

【解説】stack()メソッドでfirst,secondが同じindex同士のA,Bを3th indexに変換している。

「スタック」DATAFRAMEまたはシリーズ(持つMultiIndexように index)、逆の操作のstack()IS unstack()、どのunstacksデフォルトで最後のレベル:

stacked.unstack()
A B
first second
bar one 0.681572 0.922410
two -0.273510 -1.867662
baz one -0.072902 0.386001
two -1.570309 -0.933379
stacked.unstack(1)
second one two
first
bar A 0.681572 -0.273510
B 0.922410 -1.867662
baz A -0.072902 -1.570309
B 0.386001 -0.933379
stacked.unstack(0)
first bar baz
second
one A 0.681572 -0.072902
B 0.922410 0.386001
two A -0.273510 -1.570309
B -1.867662 -0.933379
stacked.unstack(2)
A B
first second
bar one 0.681572 0.922410
two -0.273510 -1.867662
baz one -0.072902 0.386001
two -1.570309 -0.933379

【解説】unstack()を使ってマルチインデックスからcolumnに展開できる

ピボットテーブル

ピボットテーブルのセクションを参照してください。

df = pd.DataFrame({'A': ['one', 'one', 'two', 'three'] * 3,
                    'B': ['A', 'B', 'C'] * 4,
                    'C': ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 2,
                    'D': np.random.randn(12),
                    'E': np.random.randn(12)})
df
A B C D E
0 one A foo -0.264204 0.644464
1 one B foo 0.725091 1.411540
2 two C foo 0.685939 0.292828
3 three A bar 0.902787 1.326445
4 one B bar 0.213242 0.233129
5 one C bar -1.297688 0.455064
6 two A foo 1.245784 0.281448
7 three B foo 0.453100 0.087195
8 one C foo 0.275489 -0.662055
9 one A bar -1.138988 -0.573590
10 two B bar -0.674417 -0.639242
11 three C bar 0.099451 0.757837

このデータからピボットテーブルを非常に簡単に作成できます。

pd.pivot_table(df, values='D', index=['A', 'B'], columns=['C'])
C bar foo
A B
one A -1.138988 -0.264204
B 0.213242 0.725091
C -1.297688 0.275489
three A 0.902787 NaN
B NaN 0.453100
C 0.099451 NaN
two A NaN 1.245784
B -0.674417 NaN
C NaN 0.685939

【解説】ピボットテーブルが簡単はいいけど説明がないとわからん! * 集計する列 values='D' * インデックス index=['A', 'B'] * 列 columns=['C']

D列が使われている。A,B列がマルチインデックスになる。C列が列として扱うそれで? * A,B列でマルチインデックスになっておりソートされている。 * C列のデータをもとにして種類があるだけ列データとして展開する。 * D列の内容を当てはめるという感じか

df = pd.DataFrame({'氏名': ['Aさん', 'Aさん', 'Bさん', 'Cさん'] * 3,
                    '年度': ['2017', '2018', '2019'] * 4,
                    '半期': ['上期', '上期', '上期', '下期', '下期', '下期'] * 2,
                    '出張数': np.random.randint(0, 20, size=12),
                    'レシオ': np.random.randn(12)})
df
氏名 年度 半期 出張数 レシオ
0 Aさん 2017 上期 17 -1.333364
1 Aさん 2018 上期 19 0.556026
2 Bさん 2019 上期 19 -0.196643
3 Cさん 2017 下期 9 1.896971
4 Aさん 2018 下期 14 -1.753202
5 Aさん 2019 下期 10 -0.348465
6 Bさん 2017 上期 4 -0.884795
7 Cさん 2018 上期 18 0.002419
8 Aさん 2019 上期 1 0.292434
9 Aさん 2017 下期 13 -0.913100
10 Bさん 2018 下期 2 -0.892237
11 Cさん 2019 下期 2 -0.023665
pd.pivot_table(df, values='出張数', index=['氏名', '年度'], columns=['半期'],fill_value=0)
半期 上期 下期
氏名 年度
Aさん 2017 17 13
2018 19 14
2019 1 10
Bさん 2017 4 0
2018 0 2
2019 19 0
Cさん 2017 0 9
2018 18 0
2019 0 2

【解説】やっぱり、one、two、さん、しーとかじゃ!わからんて。適当に当てはめてみました。どうでしょうか。出張がなかったのでindexには上がってこないためNaNになっているのは、理由がはっきりしているのでfill_value=0で置き換えております。

chiyoh.hatenablog.com
chiyoh.hatenablog.com

Pandas 集中講座 その4 マージ(結合)

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

Pandas 集中講座(4)

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」を実際にやってみる (4/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」を実際にやってみる

6.マージ

CONCAT

Pandasは、結合/マージ型操作の場合に、Series、DataFrame、およびPanelオブジェクトを、インデックスおよびリレーショナル代数機能のためのさまざまな種類の設定ロジックと簡単に組み合わせるためのさまざまな機能を提供します。マージセクションを参照してください。

以下と一緒にパンダオブジェクトを連結するconcat():

df = pd.DataFrame(np.random.randn(10, 4))
df
0 1 2 3
0 0.604099 0.990079 -0.521005 0.794407
1 -1.028698 -0.426349 -1.379724 -0.316860
2 1.980853 -1.300201 1.245424 1.271737
3 1.547143 0.859297 0.453607 0.972436
4 0.976275 0.075214 0.576674 -1.065869
5 0.143601 -1.579016 -0.335929 -0.560030
6 1.771954 -0.239358 -0.652136 -0.414211
7 0.107051 0.061522 0.685471 1.188601
8 1.958523 -0.195262 -0.214413 0.463809
9 -0.913350 -0.467059 1.669981 -0.923343
pieces = [df[:3], 0*df[7:], df[3:7]]
pieces
[          0         1         2         3
 0  0.604099  0.990079 -0.521005  0.794407
 1 -1.028698 -0.426349 -1.379724 -0.316860
 2  1.980853 -1.300201  1.245424  1.271737,      0    1    2    3
 7  0.0  0.0  0.0  0.0
 8  0.0 -0.0 -0.0  0.0
 9 -0.0 -0.0  0.0 -0.0,           0         1         2         3
 3  1.547143  0.859297  0.453607  0.972436
 4  0.976275  0.075214  0.576674 -1.065869
 5  0.143601 -1.579016 -0.335929 -0.560030
 6  1.771954 -0.239358 -0.652136 -0.414211]

【解説】indexをスライスしてDataFrameを3つに分ける。0~2,3~6,7~9、アクセントとして7~9は、0を書けてゼロのスカラーにしてみた。それをリスト化した。

p_df = pd.concat(pieces)
p_df
0 1 2 3
0 0.604099 0.990079 -0.521005 0.794407
1 -1.028698 -0.426349 -1.379724 -0.316860
2 1.980853 -1.300201 1.245424 1.271737
7 0.000000 0.000000 0.000000 0.000000
8 0.000000 -0.000000 -0.000000 0.000000
9 -0.000000 -0.000000 0.000000 -0.000000
3 1.547143 0.859297 0.453607 0.972436
4 0.976275 0.075214 0.576674 -1.065869
5 0.143601 -1.579016 -0.335929 -0.560030
6 1.771954 -0.239358 -0.652136 -0.414211

【解説】ちょっとアレンジを加えた。pd.concatを使うことで、DataFrameの3つのリストが1つのDataFrameになった。 リストの頭から、順にindexに入っていることがわかる。元々付けられているIndexラベルがそのままになっている。

t = (p_df[0][8], p_df[1][8])
t
(0.0, -0.0)
t[0] == t[1]
True

【コメント】index=8ラベルで0掛けた結果-0と0と分離してしまった。 [https://ja.wikipedia.org/wiki/IEEE_754%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E8%B2%A0%E3%81%AE%E3%82%BC%E3%83%AD]

参加する

SQLスタイルのマージ データベーススタイルの結合の節を参照してください。

left = pd.DataFrame({'key': ['foo', 'foo'], 'lval': [1, 2]})
right = pd.DataFrame({'key': ['foo', 'foo'], 'rval': [4, 5]})
left
key lval
0 foo 1
1 foo 2
right
key rval
0 foo 4
1 foo 5
pd.merge(left, right, on='key')
key lval rval
0 foo 1 4
1 foo 1 5
2 foo 2 4
3 foo 2 5

【解説】pd.mergeでマージである。引数でleft、次にrightそして、on='key'となっている。つまり、ベースはleft、追加はright、マージキーは'key'となり、indexを上から見ていく。マージなので2つのleft、right共に同じキーの時出力され同じ出でなければ破棄される。

l = 0
for lkey in left['key']:
    r =0
    for rkey in right['key']:
        if lkey == rkey:
            print(lkey,left['lval'][l],right['rval'][r])
        r+=1
    l+=1
foo 1 4
foo 1 5
foo 2 4
foo 2 5

【解説】な感じに、leftとrightでの2重ループで同じキーかどうか確認して、同じであれば、マージして出力する。なので、keyの値が同じだと総当たりの組み合わせが出力されることになる。

pd.merge(left, right)
key lval rval
0 foo 1 4
1 foo 1 5
2 foo 2 4
3 foo 2 5

与えることができるもう一つの例は次のとおりです。

left = pd.DataFrame({'key': ['foo', 'bar'], 'lval': [1, 2]})
right = pd.DataFrame({'key': ['foo', 'bar'], 'rval': [4, 5]})
left
key lval
0 foo 1
1 bar 2
right
key rval
0 foo 4
1 bar 5
pd.merge(left, right, on='key')
key lval rval
0 foo 1 4
1 bar 2 5

【解説】今度のマージデータは、キーが一意なのでfooとbarが1ずつしかないので横付けマージと同じ結果になる。

追加

データフレームに行を追加します。付録を参照してください 。

df = pd.DataFrame(np.random.randn(8, 4), columns=['A', 'B', 'C', 'D'])
df
A B C D
0 1.667774 -0.436216 -1.308609 -0.271015
1 0.801015 0.398531 0.215097 -1.087617
2 1.765974 -0.364113 -0.033961 1.117321
3 1.205805 0.155813 -0.550467 1.129920
4 -0.713234 -0.595935 -0.266757 1.690120
5 -1.172786 -0.069766 0.653920 -0.537090
6 -0.619824 -0.507945 -0.369917 0.600396
7 -1.825166 2.128321 1.500123 -1.485171
s = df.iloc[3]
s
A    1.205805
B    0.155813
C   -0.550467
D    1.129920
Name: 3, dtype: float64
df.append(s, ignore_index=True)
A B C D
0 1.667774 -0.436216 -1.308609 -0.271015
1 0.801015 0.398531 0.215097 -1.087617
2 1.765974 -0.364113 -0.033961 1.117321
3 1.205805 0.155813 -0.550467 1.129920
4 -0.713234 -0.595935 -0.266757 1.690120
5 -1.172786 -0.069766 0.653920 -0.537090
6 -0.619824 -0.507945 -0.369917 0.600396
7 -1.825166 2.128321 1.500123 -1.485171
8 1.205805 0.155813 -0.550467 1.129920

【解説】df.appendを使ってindexの最後に行を追加する。追加するのは、スライスされたindex=3ラベルのDataFrame。もちろんindexラベル3が付いているが、これを無視して最後に追加自動附番で8が割り当てられている。

df.append(s)
A B C D
0 1.667774 -0.436216 -1.308609 -0.271015
1 0.801015 0.398531 0.215097 -1.087617
2 1.765974 -0.364113 -0.033961 1.117321
3 1.205805 0.155813 -0.550467 1.129920
4 -0.713234 -0.595935 -0.266757 1.690120
5 -1.172786 -0.069766 0.653920 -0.537090
6 -0.619824 -0.507945 -0.369917 0.600396
7 -1.825166 2.128321 1.500123 -1.485171
3 1.205805 0.155813 -0.550467 1.129920

【解説】ignore_index=Trueを追加しないと元からついているindexが代入される。

chiyoh.hatenablog.com
chiyoh.hatenablog.com

Pandas 集中講座 その3 欠陥データと操作

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

Pandas 集中講座(3)

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」を実際にやってみる (3/6)

4.欠落データ

Pandasは主にnp.nan欠損値を表すために値を使用します。デフォルトでは計算に含まれていません。欠損データセクションを参照してください。

インデックスを再作成すると、指定した軸のインデックスを変更/追加/削除できます。これはデータのコピーを返します。

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

reindexを使って作り直す

df1 = df.reindex(index=dates[0:4], columns=list(df.columns) + ['E'])
df1
A B C D F G E
2019-05-01 0.000000 0.000000 -0.871839 5 1 1.0 NaN
2019-05-02 -0.092376 0.036220 -0.146521 5 2 2.0 NaN
2019-05-03 -0.307696 -0.051588 -0.413379 5 3 3.0 NaN
2019-05-04 0.397105 1.731472 -1.033554 5 4 NaN NaN

【解説】reindexを使ってindexとcolumnをリビルドしている。E列は、dataが指定されてないのでいわゆる未定義とかNULLの状態

df1.loc[dates[0]:dates[1], 'E'] = 1
df1
A B C D F G E
2019-05-01 0.000000 0.000000 -0.871839 5 1 1.0 1.0
2019-05-02 -0.092376 0.036220 -0.146521 5 2 2.0 1.0
2019-05-03 -0.307696 -0.051588 -0.413379 5 3 3.0 NaN
2019-05-04 0.397105 1.731472 -1.033554 5 4 NaN NaN

【解説】ラベル(.loc)を使って行選択し'E'列に1を代入

データが欠落している行を削除します。

df1.dropna(how='any')
A B C D F G E
2019-05-01 0.000000 0.00000 -0.871839 5 1 1.0 1.0
2019-05-02 -0.092376 0.03622 -0.146521 5 2 2.0 1.0

【解説】dropnaを使ってN/Aを落としている。df1全体からNaNのデータを以外を選択

【余談】判断結果としてのNaN以外に、取り込んだデータがNaNである理由は、測定ミス、測定忘れ、記録忘れ、データが飛び値になっていて明らかに記録読み取りミスなどいろいろ考えられるがこういう値に対して適当に自分に都合がよい値を入れてしまうことを改竄といいます。学校の先生方は、よくテスト問題でこれを実行してテスト問題を作ってます。あれは、都合の良い答えを作ってそれをもとに問題を作成していく手法なのかもしれませんが(いやいやあれは、合わせこみっていうものだよ!)。話がそれました

欠落データを埋める。

df1.fillna(value=5)
A B C D F G E
2019-05-01 0.000000 0.000000 -0.871839 5 1 1.0 1.0
2019-05-02 -0.092376 0.036220 -0.146521 5 2 2.0 1.0
2019-05-03 -0.307696 -0.051588 -0.413379 5 3 3.0 5.0
2019-05-04 0.397105 1.731472 -1.033554 5 4 5.0 5.0

【解説】df1全体からNaNを探しvalue値を代入する。

【余談】Seriesや、DataFrameにするデータのNaNの扱いは注意しましょう。Pandasは、NaNの扱いが上手みたいなのでいろいろできます。NaNの値をどういう理由で変更するのかソースコードコメントとして残していくのがベターと思われます。後で、データの改竄といわれないように

NaN値が格納されているブールマスクを取得します

pd.isna(df1)
A B C D F G E
2019-05-01 False False False False False False False
2019-05-02 False False False False False False False
2019-05-03 False False False False False False True
2019-05-04 False False False False False True True

【解説】isnaを使い。Is N/A? ってことなんでしょう。"Not Applicable" "Not Available" え?"Not a Number"とちがうじゃん!と思うのですが適応範囲外の値ということでは同じなんでしょうね。True/False(真/偽)(Yes/No)(合否)、、、Trueの部分がNaNが入っている箇所です
【余談】isna他でもう説明したような

pd.notna(df1)
A B C D F G E
2019-05-01 True True True True True True True
2019-05-02 True True True True True True True
2019-05-03 True True True True True True False
2019-05-04 True True True True True False False

【解説】ちなみにnotnaは「NaNじゃない」です。

5.操作

バイナリ演算の基本セクションを参照してください。

統計

一般的な操作は、欠けているデータを除外します。

記述統計を実行します

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
df.mean()
A    0.365808
B    0.402788
C   -0.552898
D    5.000000
F    3.500000
G    2.000000
dtype: float64

【解説】mean() 平均です。列毎に集計します。G列の平均が2になってます。(1+2+3)/6で1ではなく、(1+2+3)/3で2になってます。素晴らしい

他の軸でも同じ操作です。

df.mean(1)
2019-05-01    1.021360
2019-05-02    1.466221
2019-05-03    1.704556
2019-05-04    2.019005
2019-05-05    2.407438
2019-05-06    2.201831
Freq: D, dtype: float64

【解説】df.mean()では列毎でしたが、df.mean(1)では、行(index)毎になります。

df.mean(0)
A    0.365808
B    0.402788
C   -0.552898
D    5.000000
F    3.500000
G    2.000000
dtype: float64

【解説】何が軸なの?に対してdf.mean(0)だと列毎になります。つまりNumpy風axis=0,1という意味ですね。

次元が異なり、位置合わせが必要なオブジェクトを操作します。さらに、Pandasは指定された次元に沿って自動的にブロードキャストします。

s = pd.Series([1, 3, 5, np.nan, 6, 8], index=dates).shift(2)
s
2019-05-01    NaN
2019-05-02    NaN
2019-05-03    1.0
2019-05-04    3.0
2019-05-05    5.0
2019-05-06    NaN
Freq: D, dtype: float64

【解説】shift(2)で下側に2つシフトしています。上2つは定義無いのでNaNです

s = pd.Series([1, 3, 5, np.nan, 6, 8], index=dates)
s
2019-05-01    1.0
2019-05-02    3.0
2019-05-03    5.0
2019-05-04    NaN
2019-05-05    6.0
2019-05-06    8.0
Freq: D, dtype: float64
s=s.shift(2)
s
2019-05-01    NaN
2019-05-02    NaN
2019-05-03    1.0
2019-05-04    3.0
2019-05-05    5.0
2019-05-06    NaN
Freq: D, dtype: float64

【解説】ですです。shiftしてもindex値は変更しないことに注意してください

s=s.shift(10)
s
2019-05-01   NaN
2019-05-02   NaN
2019-05-03   NaN
2019-05-04   NaN
2019-05-05   NaN
2019-05-06   NaN
Freq: D, dtype: float64

【余談】あうあう

s = pd.Series([1, 3, 5, np.nan, 6, 8], index=dates).shift(-2)
s
2019-05-01    5.0
2019-05-02    NaN
2019-05-03    6.0
2019-05-04    8.0
2019-05-05    NaN
2019-05-06    NaN
Freq: D, dtype: float64

【解説】-2で上側に2つシフト

s.shift(1)
2019-05-01    NaN
2019-05-02    5.0
2019-05-03    NaN
2019-05-04    6.0
2019-05-05    8.0
2019-05-06    NaN
Freq: D, dtype: float64

【解説】シフトしたデータは、消えているので戻りません。

df.sub(s, axis='index')
A B C D F G
2019-05-01 -5.000000 -5.000000 -5.871839 0.0 -4.0 -4.0
2019-05-02 NaN NaN NaN NaN NaN NaN
2019-05-03 -6.307696 -6.051588 -6.413379 -1.0 -3.0 -3.0
2019-05-04 -7.602895 -6.268528 -9.033554 -3.0 -4.0 NaN
2019-05-05 NaN NaN NaN NaN NaN NaN
2019-05-06 NaN NaN NaN NaN NaN NaN

【解説】df.subって、一瞬Main/Subとかで、範囲取り出しの処理かと思ったら。subtraction(引き算)のことじゃん!sのSeriesとdfのindexをマッチングさせて引き算している。ってsubtractがあるが・・・

df.subtract(s, axis='index')
A B C D F G
2019-05-01 -5.000000 -5.000000 -5.871839 0.0 -4.0 -4.0
2019-05-02 NaN NaN NaN NaN NaN NaN
2019-05-03 -6.307696 -6.051588 -6.413379 -1.0 -3.0 -3.0
2019-05-04 -7.602895 -6.268528 -9.033554 -3.0 -4.0 NaN
2019-05-05 NaN NaN NaN NaN NaN NaN
2019-05-06 NaN NaN NaN NaN NaN NaN

適用する

import pandas as pd
import numpy as np
dates = pd.date_range('20130101', periods=6)
df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD'))
df
A B C D
2013-01-01 -1.174480 0.131666 0.615321 -0.674131
2013-01-02 0.360878 0.286958 -0.901880 -1.644234
2013-01-03 0.175645 0.967822 -0.817930 -0.160181
2013-01-04 -2.190213 -0.217229 0.158892 0.891357
2013-01-05 0.408453 0.685262 2.027146 -0.426742
2013-01-06 0.909910 -0.743968 0.980539 -1.879472

データに関数を適用する:

df.apply(np.cumsum)
A B C D
2013-01-01 -1.174480 0.131666 0.615321 -0.674131
2013-01-02 -0.813602 0.418624 -0.286558 -2.318364
2013-01-03 -0.637957 1.386446 -1.104489 -2.478546
2013-01-04 -2.828170 1.169217 -0.945597 -1.587188
2013-01-05 -2.419717 1.854479 1.081549 -2.013930
2013-01-06 -1.509807 1.110511 2.062088 -3.893402

【解説】np.cumsumは、累積加算する関数。上から順に足していった値になる。

np.cumsum([1,2,3,4,5,6,7,8,9,10])
array([ 1,  3,  6, 10, 15, 21, 28, 36, 45, 55], dtype=int32)
np.sum([1,2,3,4,5,6,7,8,9,10])
55

【解説】np.cumsumは、使ったことなかったので、リストを入れた結果を表示してみた。予想通り。つまり、列毎にcumsumが適応されて実行されたということになる。オブジェクト指向なのでcumsumcumsum()は違うもの、名詞と動詞みたいな感じか。前者がメソッドの自身を示し、後者が実行する関数を示している感じ。cumsumを使って適応して!っていうのがdf.apply(np.cumsum)の意味合い。

np.cumsum(df)
A B C D
2013-01-01 -1.174480 0.131666 0.615321 -0.674131
2013-01-02 -0.813602 0.418624 -0.286558 -2.318364
2013-01-03 -0.637957 1.386446 -1.104489 -2.478546
2013-01-04 -2.828170 1.169217 -0.945597 -1.587188
2013-01-05 -2.419717 1.854479 1.081549 -2.013930
2013-01-06 -1.509807 1.110511 2.062088 -3.893402
type(np.cumsum(df))
pandas.core.frame.DataFrame

【解説】であれば、本来の使い方np.cumsum(df)としたら?あ、できた。dfをnp.cumsum()が扱える値にcastして実行しているはず。が、戻り結果はpandas.core.frame.DataFrameのまま?なんでしょうね。しかし、微妙に結果が変わっていて。演算はfloat扱いになってますね。

【コメント】お!出たなlambdaだ。謎な引数をもとに演算して結果を謎な出力に戻すということをしているやつですね。 df.applyをちょっと調べると。axis = 0が、デフォなので列毎にSeriesにを切り出して処理をしている。df.applyは、Series切り出しオブジェクトを入力として与え、結果を受け取るということか。lambdaを何回も回るのではなく1回適応で終わり。

df.apply(lambda x: x.max() - x.min())
A    3.100123
B    1.711790
C    2.929026
D    2.770829
dtype: float64
df['A'].max() - df['A'].min()
3.100122888374953

【解説】上記の結果をcolumn毎に実行した結果になったと

ヒストグラム

Histogramming and Discreizationsで詳細を参照してください。

s = pd.Series(np.random.randint(0, 7, size=10))
s
0    0
1    3
2    2
3    1
4    4
5    4
6    2
7    6
8    6
9    1
dtype: int32
s.value_counts()
6    2
4    2
2    2
1    2
3    1
0    1
dtype: int64
type(s.value_counts())
pandas.core.series.Series

【コメント】おい!終わりかよ。きっと苦手な分野なのかな?

s.value_counts().index
Int64Index([6, 4, 2, 1, 3, 0], dtype='int64')

【解説】ヒストグラム?頻度を数えている。上記のSeriesに3が3回出現し、6が2回、、、という結果を違うSeriesに戻してる。作られているindexはintになっている

s = pd.Series(np.random.randn(1000))
s.value_counts(bins=10,sort=False)
(-3.017, -2.416]       6
(-2.416, -1.822]      31
(-1.822, -1.228]      68
(-1.228, -0.634]     158
(-0.634, -0.0396]    237
(-0.0396, 0.555]     247
(0.555, 1.149]       154
(1.149, 1.743]        64
(1.743, 2.337]        28
(2.337, 2.931]         7
dtype: int64

【解説】Numpyで1000個乱数を作成し10区画に分けたヒストグラムを作成。瓶の範囲が明確に表示されているのがいい感じですね。また、s.value_countsの目的がSeriesで同じ値になるものを多い順に並べなさいという関数ですね。indexがそのキーでdataが頻度になる。出てくる結果が多いbest5を調べるときとか便利そうですね

文字列メソッド

以下のコードスニペットのように、seriesはstr属性に一連の文字列処理メソッドを備えているため、配列の各要素に対する操作が簡単になります。strのパターンマッチングは一般にデフォルトで正規表現を使います(そして場合によっては常にそれらを使います)。詳しくはベクトル化された文字列メソッドをご覧ください。

【解説】コードスニペット(code snippet)ソースコードの断片

s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])
s
0       A
1       B
2       C
3    Aaba
4    Baca
5     NaN
6    CABA
7     dog
8     cat
dtype: object
s.str.lower()
0       a
1       b
2       c
3    aaba
4    baca
5     NaN
6    caba
7     dog
8     cat
dtype: object

【解説】Seriesのdata形式が文字列の場合いろいろと便利な関数が用意されているよ。ということですね。上記の例は、各データを小文字化するlower()という関数です。注意点としては、NumpyとPandasでは文字をstrとして認識せず、数値以外のものでしょうがなく扱っているオブジェクト型として認識しているのでその辺が注意です。dtypeがstrではなくobjectです

type('ABC')
str
s.str.len()
0    1.0
1    1.0
2    1.0
3    4.0
4    4.0
5    NaN
6    4.0
7    3.0
8    3.0
dtype: float64

【コメント】Excelでもそうですがセルに入っている文字列を処理することを多いですね。
詳しくはベクトル化された文字列メソッドをご覧ください。の部分時間がある時に調べるといろいろとできる幅が広がりそうです。区切り位置とかどうやるんだろ。columnを横断してしまうけどなどと思考をはしらせてしまいますね。

chiyoh.hatenablog.com
chiyoh.hatenablog.com