[Python] EelでExcelの表から特定の項目を抽出するアプリを作る

# はじめに
はじめまして、未経験/独学からフロントエンドエンジニアとしての就職を目指しているYuuhaと申します。
技術力向上のため、日々新しい技術を取り入れて学習するようにしています。

今回は、デスクトップアプリを一度作成してみたいと思っていたので、Eelを使用してExcelを扱うアプリを作った時の話です。

# 制作の背景
何か問題解決のために技術を使いたいと思い、身内に聞き取りをしてみたところ、「Excelの表から特定の項目だけ抽出して新しいExcelに変換する機能が欲しい」ということでアプリの内容が決まりました。

Eelを選んだ理由は単純で、Pythonなら少し触ったことがあったこと、早くリリースするために学習コストがかからないHTML,CSSでUIが実装できるという点がポイントでした。

# 制作したもの
今回制作したものは、学習目的かつ、実際に機能することと早くリリースすることを優先していたので、プロダクトとしてのクオリティは保証できないので、その点はご了承ください。

https://github.com/ds-yuuha/excel_table/tree/main


# アプリの概要
アプリの概要は下のような表があった場合、特定の項目(列)を抽出したい(例えば、itemAとitemDの列だけ抜き出して2×2の表を作る)という簡単なものです。
|  | itemA | itemB | itemC | itemD |
|:-:|:-:|:-:|:-:|:-:|
| 1 | A-1 | B-1 | C-1 | D-1 |
| 2 | A-2 | B-2 | C-2 | D-2 |

表が上記のようなパターンのみであれば、PythonのライブラリのPandasを利用すれば簡単に実現できます。ただし、大きく分けて以下の3パターンのような特殊なパターンの表があったので、その場合にも対応できるようにしたいと思います。

### 1.表の項目が複数ある場合
以下のような表の場合、itemCは合計の値(itemCの最後の列)だけ抽出します。
<table align="center">
  <thead>
    <tr>
      <th rowspan="2"></th> <th rowspan="2">itemA</th> <th rowspan="2">itemB</th> <th colspan="4" style="text-align: center">itemC</th>
    </tr>
    <tr>
      <th style="text-align: center">itemC-1</th> <th style="text-align: center">itemC-2</th> <th style="text-align: center">itemC-3</th> <th style="text-align: center">合計</th>
    </tr>
  </thead>
  <tr>
    <td> 1 </td> <td style="text-align: center">A-1</td> <td style="text-align: center">B-1</td> <td style="text-align: center">C-11</td> <td style="text-align: center">C-12</td> <td style="text-align: center">C-13</td> <td style="text-align: center">C-sum1</td>
  </tr>
  <tr>
    <td> 2 </td> <td style="text-align: center">A-1</td> <td style="text-align: center">B-1</td> <td style="text-align: center">C-11</td> <td style="text-align: center">C-22</td> <td style="text-align: center">C-23</td> <td style="text-align: center">C-sum2</td>
  </tr>
</table>

### 2.Excelのシート1枚に対して複数の表が存在する場合
以下のような場合、各列の項目が一致しないので、各表ごとに分割して処理する必要があります。
|  |  |  |  |
|:-:|:-:|:-:|:-:|
|  | itemA | itemB | itemC |
| 1 | A-1 | B-1 | C-1 |
| 2 | A-2 | B-2 | C-2 |
|  |  |  |  |
|   | itemA | itemD | itemE |
| 1 | A-1 | D-1 | E-1 |
| 2 | A-2 | D-2 | E-2 |

### 3.目的となる表以外に関係のない表が存在する
こちらも似たような処理ですが、必要ない表は無視して進める処理が必要になります。
(この場合、上の表が必要ないものになります)
|  |  |  |  |
|:-:|:-:|:-:|:-:|
|  | 単価 | 数量 | 単位 |
| 1 | 100 | 5 | kg |
| 2 | 200 | 2 | L |
|  |  |  |  |
|   | itemA | itemB | itemC |
| 1 | A-1 | B-1 | C-1 |
| 2 | A-2 | B-2 | C-2 |

以上の3パターンに対応できるように実装しました。次の項で、今回それぞれのパターンに合わせて行った実装を紹介します。

# Excel処理部分の実装の概要
### 複数の表がある場合、各表ごとに分割する
詳しい解説は省きますが、groupby()によって空白の行があればそこを境界として表を分割し、dropna()で空白行を消去しています。

```python:
input_file = pd.ExcelFile(pathnames)
    sheet_names = input_file.sheet_names
    
    for sheet_name in sheet_names:
        main_columns = {}
        for i in range(max):
            main_columns[i] = {}
        df = pd.read_excel(p,index_col=None,header=None,sheet_name=sheet_name)
        filtered_df = df.iloc[1:,:end]
        dfg = filtered_df.groupby((df.isnull().all(axis=1)).cumsum())

        for index, g in dfg:
            g = g.dropna(how="all")
            if len(g) > 0:
                g = g.values
                d = pd.DataFrame(g[1:,:], columns=g[0])
            
```

### 分割した表をさらに項目ごとに分解する
下記で登場するdivisionsは列の項目の始点の番号です。したがって、2つ目以降のdivisionsの1つ前の番号は、1つ前の項目の最終列になります。なので、下記のようにcolumn_itemはすべての列の項目の最終列に該当します。

```python:
                    #上記の続き
                    num = 0
                        prev = 0
                        for i in divisions:
                            if i >= 1:
                                if num == 0:
                                    column_item = d.iloc[:,0:i]
                                else:
                                    column_item = d.iloc[:,prev:i]
                                num += 1
                                prev = i

```

実装の基本的な考え方は以上となります。あとは、抽出したい項目などをsqlite3などのデータベースを使用して記録・呼び出しすることで、表の抽出ができるようにしました。

# まとめ
以上が今回制作したデスクトップアプリの概要でした。
より良い実装方法や間違いなどございましたら、コメントなどでご指摘いただけると幸いです。
もし、このアプリを使用したいという方は、githubのリポジトリからdist.zipにexeファイルが入っているので、そちらをご利用ください。(exeファイル化には、pyinstallerを使用しているので、ウイルスファイル扱いされる可能性があります。その点はご注意ください。)

アプリの内容自体は簡単なものでしたが、新しい言語を学習することで、これまで触っていた言語の復習になったり、新しい発見があったりしたので、これからもいろいろな分野に触れていこうと思います。