PythonのPandasライブラリでExcelの集計が簡単にできる?

f:id:ts0818:20210123161714j:plain

VisiCalc(ヴィジカルク)は、世界初のパーソナルコンピュータ向け表計算ソフトである。それまでホビー用と考えられていたマイクロコンピュータをビジネスツールとして定着させるきっかけとなったソフトウェアである。6年間で70万本を売り上げた。

VisiCalc - Wikipedia

1979年、Personal Software 社(後のビジコープ)が Apple II 向けに発売した VisiCalc は、ダン・ブリックリン考案、ボブ・フランクストン設計、彼らの Software Arts 社開発によるものであった。

VisiCalc - Wikipedia 

電子式表計算は画期的なアイデアだったが、ブリックリンはこのアイデアでは特許を取れないだろうと助言され、この発明から得られたであろう莫大な利益を逃してしまった。当時、アメリカ合衆国ではソフトウェアの特許は認められておらず、権利は著作権でのみ守られるとされていた。著作権はアイデアそのものを守るのには適しておらず、競合他社はコンセプトを即座にコピーして表示形式を変えるだけで著作権侵害を問われずに販売することができたのである。ただしVisiCalcが開発された当時、表計算ソフト自体は大型コンピュータの世界で既に存在していたことから、仮にソフトウェア特許が認められていたとしても、どのみちブリックリンらが特許を取得することはできなかっただろうという意見も有る

VisiCalc - Wikipedia

間もなく VisiCalc よりも強力な他社製品が登場した。SuperCalc(1980年)、マイクロソフトMultiplan(1982年)、Lotus 1-2-3(1983年)、ClarisWorks1984年)の表計算モジュールなどである。そして、Microsoft ExcelMac OS版が1985年、Windows 2.0 版が 1987年)に至って表計算ソフトは新世代へ移行していった。特許が成立していなかったので、これらはビジコープ社に何も支払うことがなかった。

VisiCalc - Wikipedia

イノベーションを起こしたのに報われないのが悲し過ぎる(涙)。

頑張った人に対して、優しい世界であって欲しいと思う今日この頃、どうもボクです。

というわけで、今回は、Python拡張ライブラリのPandasについてですかね。

レッツトライ~。

 

Pandasは表計算に特化してそう?

Pandasの公式で公開されてるCheet Sheet を見た感じだと、どうも表計算を意識してそうなコンセプトっぽい気がするんですよね。

⇧ 完全に「表」で扱うようなイメージ?って言うんですかね、そんな雰囲気がひしひしと伝わってくるPDFですかね。

 

その前にPandasでExcelを扱うには?

PandasでExcelを扱うには何か他にライブラリをインストールとか必要なんでは?

ネットにある情報だと、

note.nkmk.me

⇧ PandasでExcelを扱うためには、Pandasとは別にライブラリをインストールする必要があるみたいですね。

ただ、

dev.classmethod.jp

⇧ 上記サイト様によりますと、「xlrd」ってライブラリがメンテナンスされなくなってる状況らしく、「opnepyxl」ってライブラリを使った方が良いってことみたいね。

Pandasのほうでも、

pandas.pydata.org

These are the changes in pandas 1.2.0. See Release notes for a full changelog including other versions of pandas.

Warning

The xlwt package for writing old-style .xls excel files is no longer maintained. The xlrd package is now only for reading old-style .xls files.

Previously, the default argument engine=None to read_excel() would result in using the xlrd engine in many cases, including new Excel 2007+ (.xlsx) files. If openpyxl is installed, many of these cases will now default to using the openpyxl engine. See the read_excel() documentation for more details.

Thus, it is strongly encouraged to install openpyxl to read Excel 2007+ (.xlsx) files. Please do not report issues when using ``xlrd`` to read ``.xlsx`` files. This is no longer supported, switch to using openpyxl instead.

Attempting to use the xlwt engine will raise a FutureWarning unless the option io.excel.xls.writer is set to "xlwt". While this option is now deprecated and will also raise a FutureWarning, it can be globally set and the warning suppressed. Users are recommended to write .xlsx files using the openpyxl engine instead.

https://pandas.pydata.org/docs/whatsnew/v1.2.0.html#what-s-new-in-1-2-0-december-26-2020

⇧ 同じように、「openpyxl」を推奨してますね。

で、データサイエンス向けのディストリビューションのAnaconda でPythonを使ってる場合は、デフォルトで「Pandas」とか「openpyxl」とかインストール済みの状態であるらしい。

なので、Visual Studio Codeとかを使ってる場合は、自分でインストールする必要があるっぽい感じですかね。

 

Pandas で用意されてるメソッドを活用しないと?

PandasのDataFrame に用意されてるメソッドを知ってると知らないとでは天と地ほどの差が生まれることを痛感した次第です...

例えば、手前味噌で恐縮ですが、

ts0818.hatenablog.com

⇧ こちらの記事で、

  • シート1で条件に一致する集団を抽出する
  • その抽出した集団に一致する要素を、更に別のシートであるシート2に存在するか比較する
  • シート1とシート2のどちらにも存在した行の他の列を抽出する

っていうようなことを、PandasのDataFrame で用意されてるメソッドを活用できずにコーディングしてみた結果が以下。 (Anacondaを起動してからのJupyter Notebookを起動してPythonでコーディングしてます。)

import numpy as np
import pandas as pd

# ExcelからDataFrameオブジェクト生成
trading_master = pd.read_excel(
    "trading_table.xlsx", sheet_name="trading_master")
employee_master = pd.read_excel(
    "employee_table.xlsx", sheet_name="employee_master")

# データを複製
trading_master_copy = trading_master.copy()

# ピボットテーブルを使うためにDataFrameオブジェクトを編集
# 勤続年数の列を追加
trading_master_copy["勤続年数"] = [0] * len(trading_master_copy)
# 単価×個数の列を追加
trading_master_copy["総計(単価×個数)"] = [0] * len(trading_master_copy)
# 勤続年数の列から重複した要素を除外したリストを作成
year_of_service_range = sorted(list(set(employee_master["勤続年数"])))
# print("employee_masterの「勤続年数」のリスト:{}".format(year_of_service_range))

# employee_masterの「勤続年数」の要素数の分だけ繰り返し
for i in year_of_service_range:
  # 勤続年数毎のデータ
  employees = employee_master[employee_master["勤続年数"]==i]
  # employee_masterの「社員名」とtrading_masterの「担当者名」に一致する「勤続年数」を更新する
  for emp_name, emp_year in zip(employees["社員名"], employees["勤続年数"]):
    for trade_emp_name, trade_emp_year in zip(trading_master_copy["担当者名"], trading_master_copy["勤続年数"]):
      if emp_name == trade_emp_name:
        # print("trade_emp_name: {}, emp_year: {}".format(trade_emp_name, emp_year))
        # queryの更新は、『SettingWithCopyWarning』というエラーが出てしまう
        # trading_master_copy.query('担当者名 == @emp_name')
        trading_master_copy.loc[trading_master_copy["担当者名"] == emp_name, "勤続年数"] = emp_year
        # 単価×個数の列に値を追加
        trading_master_copy.loc[trading_master_copy["担当者名"] == emp_name, "総計(単価×個数)"] = trading_master_copy["単価"] * trading_master_copy["個数"]
        
# print(trading_master_copy)    
# ピボットテーブル化
pd.pivot_table(trading_master_copy, index="勤続年数", values="総計(単価×個数)", margins=True, margins_name='総合計')

ってな感じで、for文が3重になっているという、かなり駄目駄目なコーディングになってしまっているというね... 

無理やり感が半端ないっすね、反省...

 

Pandasで用意されてるメソッドを活用すると

で、pandas.DataFrameで用意されてるメソッドを活用したコーディングが以下。(Anacondaを起動してからのJupyter Notebookを起動してPythonでコーディングしてます。)

import numpy as np
import pandas as pd

# ExcelからDataFrameオブジェクト生成
trading_master = pd.read_excel(
    "trading_table.xlsx", sheet_name="trading_master")
employee_master = pd.read_excel(
    "employee_table.xlsx", sheet_name="employee_master")

# カラム名を合わせる
rename_data = employee_master.rename(columns={"社員名": "担当者名"})
# ピボットテーブルを使うためにDataFrameオブジェクトを編集
merge_data = pd.merge(trading_master, rename_data[["担当者名", "勤続年数"]], how="right", on="担当者名")
# 日本語のカラム名を追加できるように、dict型でデータを用意
add_column = {"総計(単価×個数)": merge_data["単価"] * merge_data["個数"]}
# 単価×個数のカラムを追加
add_merge_data = merge_data.assign(**add_column)
# ピボットテーブル化
pd.pivot_table(add_merge_data, index="勤続年数", values="総計(単価×個数)", margins=True, margins_name='総合計')

⇧ ってな感じで、スッキリしたコードにリファクタリングできました。

もっとスッキリとしたコーディングにできそうな気もしますが、いまのところの自分の技術だと、このぐらいになってしまう感じです...

なんか、

sinhrks.hatenablog.com

⇧ 上記サイト様によりますと、DataFrame オブジェクトでカラム名が日本語だと、assign()メソッドでカラムを追加する際にひと手間いる模様。

ちなみに、

stackoverflow.com

pivot() is used for pivoting without aggregation. Therefore, it can’t deal with duplicate values for one index/column pair.

Since here your index=['struct_id','resNum'] have multiple duplicates, therefore pivot doesn't work.

However, pivot_table will work because it will handle duplicate values by aggregating them.

https://stackoverflow.com/questions/30960338/pandas-difference-between-pivot-and-pivot-table-why-is-only-pivot-table-workin

⇧ ってな感じで、「pandas.DataFrame.pivot()」と「pandas.pivot_table()」の違いは、

  • pandas.pivot()、または、pandas.DataFrame.pivot()
    集計する時とかに、列の重複とかは考慮しない
  • pandas.pivot_table()、または、pandas.DataFrame.pivot_table()
    集計する時とかに、列の重複とかも考慮してくれる

ってことみたいね。

pivot_table() メソッドを選んでおけば、安全牌ってことなんですかね?

いまいち、Pandasの「General Functions」と各クラスで用意されてる「method」の違いが分からんのだが、一般的なPythonの「method」と「function」の違いは、

www.tutorialspoint.com

Key differences between method and function in python

As we get the basic understanding of the function and method both, let's highlight the key differences between them −

  • Unlike a function, methods are called on an object. Like in our example above we call our method .i.e. “my_method” on the object “cat” whereas the function “sum” is called without any object. Also, because the method is called on an object, it can access that data within it.

  • Unlike method which can alter the object’s state, python function doesn’t do this and normally operates on it.

In Short, a method is a function which belongs to an object.

https://www.tutorialspoint.com/difference-between-method-and-function-in-python

⇧ ってな感じで、超要約すると、

  • オブジェクトに属するfunctionはmethod
  • それ以外はfunction

ってことになるんですかね?

Pandasの「General Function」は、Javaで言うところのクラスが持つstaticなmethodって感じなんですかね?

モヤモヤ感しか残らんな... 

そして、毎回、調べるのに時間がかかってしまいますな... 

優秀なエンジニアのコードを写経させていただく機会に与りたいと思う今日この頃ですな。

今回はこのへんで。