3.基本操作

3_2 基本操作その2(2019/8/24)

1)基本操作(その2)の実行

次は、Pandasのデータに対応した操作方法を学習しましょう。
 1.データの読み込みと書き出し
 2.データの修正
 3.データの編集
 4.データの集計
 5.インデックスの変更
について実際のPythonプログラムとデータを操作しながら学習します。
では、Warehouseの中の3.基本操作の「基本操作その2」をダウンロードして解凍したbasic2フォルダをc:\Python\basicの下に格納してください。basic2フォルダの中には、basic2.pyプログラムとそこで使用するデータファイルが格納されています。
IPythonを稼働させて、basic2.pyをエディタで開き、コメントを無視して、1行ずつ状況を確認しながら実行してみてください。
basic2.pyの記載に倣って、独自の変数等を定義して実行してみてください。エラーになったら、エラーの状況も記録しておくことをお勧めします。あなた自身の備忘録としてノウハウを蓄積していってください。

2)DataFrameは、テーブルデータ

本ブログで使用する分析用データは、PandasのDataFrame(以降、「DF」と記載します)になります。Pandasは、NumPyを元にして開発されています。NumPyの中心となる配列は、ndarrayといいますが、n-d-アレイであり、n次元の配列を意味しています。2次元の場合は、行列ということになります。
DFは、この行列がベースになり、行や列を階層化することで多次元を表すことになりますが、DFのデータ形式は、行列ではなく、データベースのテーブルのイメージだと考えてください。表計算ソフトの1シート上の2次元のデータと考えても良いです。
本ブログでは、DFの扱い方を決めています。他の扱い方も可能ですが、色々できると考えるのは混乱のもとですし、データ分析を行う上では1つに限定した方が分かり易いと思います。
行(index)と列(column)で表され、列は、カラム名(列名、項目名、変数名の様にいいます)として定義され、行には、カラムごとに対応した複数の値を持つことになります。項目毎の複数の観察結果があるというイメージです。
DFを指定して、項目名を指定すれば、当該項目の全観察情報を得ることができますし、行も指定すれば特定の値を得ることができます。データベースの様です。
Pythonは、全ての順番が、0から始まります。行もこの順番の番号を数値で指定することもできますが、本ブログでは、行の名称で指定します。行が番号で指定されている場合も当該番号を数値ではなく、文字列の名前で指定します。なぜなら、行は削除されることもあり、順序番号で指定した場合に所望のデータを指定できないことがあるからです。

3)行と列の指定

Pandasの処理においては、当該処理を行で行うのか列で行うのかを指定する必要があります。本ブログでは、行と列の位置付けは決まっていますが、Pandas自体に決まりはありませんので、DFに対する各処理は、行と列のいずれかを指定する必要があるという事です。
Pandasでは、行はindex、列はcolumnsといいますが、通常は次元で指定します。Pythonの数字は0から始まりますので、1次元が0次元、2次元が1次元となり、行を指定する場合は、axis=0となり、列を指定する場合は、axis=1とします。
しかし、多くの人がここで混乱します。実際のPandasの事例を見ていきましょう。
    r1 r2 r3
 0 name1 123 234
 1 name2 345 456
 2 name3 567 678
 3 name4 789 890
 
この様なDF(名称がdf1)があったとします。
df1.drop(“r3”,axis=1)とすると、列名のr3の列を削除します。axis=1を指定しないと、行名にr3があるのかもしれませんから、あいまいな指定という事になってしまいます。
しかし、以下の場合はaxisの意味が異なります。
df1.mean(axis=1)とすると、行毎の平均が計算されてしまいます。
混乱しないように、以下の様に覚えてください。
先ず、DFの一部に対して処理をする場合、それが行に対してか、列に対してかを指定する場合は、行であればaxis=0とし、列であればaxis=1とします。覚えやすいパターンです。
次に、DFの全体に対して処理をする場合です。それが行毎に対して行い結果が列になるならaxis=1とし、列毎に対する処理で結果が行になるならaxis=0になります。どちらに対する処理かではなく、結果で判断するという事ですが、全体に対して処理をする場合は、逆になると覚えても問題ありません。
尚、DFにDFを結合する場合は、行方向であればaxis=0、列方向であればaxis=1とノーマルな考え方になります。

4)テーブルの結合

複数のDFを単純に連結して処理したいというニーズがあります。
例えば、同じデータ項目のn月のdf1とn+1月のdf2があり、それを行側に繋げた(n月の後にn+1月が続く)df3を作成するという場合です。これはconcatという関数を使用し、以下の様に記述します。
 df3 = pd.concat([df1,df2],axis=0) # 行方向の連結
 
もし、A商品の売上情報dfaとB商品の売上情報dfbがあって、それを列方向に連結したdfabを作成する場合は、以下になります。行方向は、indexに合わせて連結されます。
 dfab = pd.concat([dfa,dfb],axis=1) # 列方向の連結
 
データベースの処理では、複数のテーブルをキーを元に結合させて処理をすることがよくありますが、DFを活用したデータ分析でも同様に複数のDFをキーで結合して処理することがよくあります。
この様にキーを元に結合させる場合は、merge関数を使用します。
キーを元に連結するには、当該キーにindexを使用する方法と特定の列名による方法があります。また、その結合方法は、howで指定し、inner、outer、left、rightの4種類から指定します。例えば、表計算ソフトでよく使われるLookup Tableの処理は、以下の様になります。
基本となる売上データとしてDF名salesがあって、salesには商品の情報として商品コードとしてpcodeが入っているとします。しかし、商品の名称や単価については、商品テーブルであるDF名ptableに入っています。この場合、salesにptableの情報をpcodeでマージしたいと考え、以下の様に記述します。
 sales_p = pd.merge(sales,ptable,left_on=”pcode”,right_on=”pcode”,how=”left”)
 
最初の引数がleft側のDFで、次の引数がright側のDFになり、次にleft側のキー、right側のキーの順番で指定し、最後に連結方法(how)を指定します。indexをキーに使用する場合は、left_index=True、right_index=Trueの様に指定します。デフォルト値を指定しない方法等もありますが、常に同じ方法で指定することが間違いを無くす近道です。
連結方法(how)のデフォルト値はinnerになります。この場合、左側と右側のキーが一致した情報だけのDFが生成されます。outerの場合は、innerの結果に加えて、左側DFに存在したキーの全行と右側DFに存在したキーの全行が加えられます。leftの場合は、左側DFに存在したキーの全行だけが加えられ、rightの場合は、右側DFに存在したキーの全行だけが加えられます。
今回の場合は、売上データの全行をそのままにし、売上データに存在するpcodeに合致するptableの情報を結合するということからleftを指定しています。

5)オブジェクトのコピー

オブジェクトのコピーには、本当のコピーと形だけのコピーの2種類があります。例えば、aオブジェクトの情報をbオブジェクトにコピーする場合、以下の様に記述します。
 b = a
 
この方法では、形だけのコピーになります。実際の処理は、パソコンのメモリー上のアドレス空間で行われますが、形だけのコピーでは、アドレスだけがコピーされて、本体はコピーされません。つまり、bさんは、aさんの住所が複製されて、bさんの住所を尋ねるとaさんの家を見ることができるという事です。これでは、bさんの家を改築すると、aさんの家も改築されてしまいます。
 b = a.copy()
 
この方法であれば、bさんの家は、aさんとは別のアドレスで、aさんと同じ家が建つことになり、bさんの家を改築してもaさんの家には影響がありません。但し、PandasのDFに対する処理はこれで良いのですが、Pandasではない、Pythonのオブジェクトの場合は、deepcopy()という関数になりますので注意してください。形だけのコピーはshallow copy(浅いコピー)、本当のコピーはdeep copy(深いコピー)と言われます。
但し、単なるコピーではなく、処理をした結果を新しいオブジェクトに渡す場合は、この限りではありません。
例えば、DFの並べ替えをした結果を新しいDFに渡す場合は、新たなアドレスのDFとして渡されます。
 df2 = df1.sort_values(by=”key”)
 
これは、df1が並び替え前で、df2が並び替え後であることを確認することで本当のコピーがされているかを確認することができます。不明な場合は、以下の様にしてください。
 df2 = df1.処理.copy()
 
copy()を追加することで、確実に新しいアドレスにコピーされます。
新しいオブジェクトに手を加えた結果が元のオブジェクトに影響を与えたくない場合は、本来のコピーをしなければなりませんが、本来のコピーを行えばメモリーを新たにとって複製作業をすることになりますので、ビッグデータのデータ分析では、処理時間が掛かることになります。ビッグデータの分析をする場合で、処理時間が気になるような場合は、可能な限り形だけのコピーで済ませた方が良いという事があります。

6)警告への対応

Pandasで処理をしていると警告メッセージやエラーメッセージが出されることがよくあります。エラーメッセージの場合は、メッセージに記載されている内容に対応して、記載内容を改めなければなりませんが、警告メッセージの場合は、多くの場合、警告メッセージを無視しても問題ありません。
例えば、以下の様な処理を行うと警告メッセージが出されます。
 df1[“列名”][行名] = 値
 
 C:\Users\tani\Anaconda3\Scripts\ipython:1: SettingWithCopyWarning:
 A value is trying to be set on a copy of a slice from a DataFrame
 
 See the caveats in the documentation:
 http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
 
これは、SettingWithCopyWarningという事ですので、警告メッセージだということです。実際にdf1を表示して確認すると値が置き換わっています。どこが問題かというと、df1[“列名”][行名]という処理では、df1[“列名”]という処理を行い、その結果のSeriesに対して[行名]にアクセスしているという事であって、無駄な処理が行われているという事になります。
 df1.loc[行名,列名] = 値
 
とlocを使用することで、直接アクセスすることができ、警告メッセージは出なくなります。
このケースにおいては、時間が掛かるだけでしたが、正しく処理されないことがあります。このメッセージの意味するところは、「DataFrameからのスライスのコピーに値を設定しようとしています」と記載されているように、Pandasの処理において、ケースバイケースではありますが、DataFrameのスライス等(一部を切り取ったもの)の本来のコピー又は形だけのコピーに対して処理を行おうとしているという事ですので、時間が掛かるという問題だけではありません。
処理の途中で形だけのコピーが行われていれば、当該コピーに新たな処理を行えば元のデータにも影響を与えますし、処理の途中で本来のコピーが行われ、当該コピーに新たな処理を行った場合は、元のデータには影響を与えません。つまり、処理結果が反映されないケースもあるという事になります。
このSettingWithCopyWarningは、複数の処理結果に対して発生する場合もあります。警告が出た場合は、処理が正しく反映しているかを確認するようにしてください。本来は、警告が出ない処理方法に変更することをお勧めします。
基本操作(その2)の実例を実際に操作し、処理を工夫してみてください。処理方法だけでなく、警告メッセージについても実際に動かしてみることで理解が深まると思います。