PythonでExcel作った

# はじめに

データサイエンスで誰もがまず間違いなく扱うであろうテーブルデータ。[pandas](https://github.com/pandas-dev/pandas)や最近では[polars](https://github.com/pola-rs/polars)を使って解析・可視化する人も多いでしょう。一方で、データサイズがそれほど大きくないときは、インタラクティブに編集・プロットしたり、セルの中でちゃちゃっと平均とかを計算できるExcelの方が便利な場合が多いです。

[pandasGUI](https://github.com/adamerose/PandasGUI)というものもあるのですが、かなり操作性が悪いし、開発もそんなホットではないですね...

テーブルデータをExcelみたくいじりながら、いつでも`DataFrame`でデータを回収してPythonで解析できるソフトがあったらいいなあと思ったので、`tabulous`というのを作りました。この記事ではこれを簡単に紹介したいと思います。

https://github.com/hanjinliu/tabulous

名前はtabularとfabulousを掛けたものです。GUIはQtで作っています。

なお、詳しいドキュメント（英語）は[こちら](https://hanjinliu.github.io/tabulous/)にあります。

# `tabulous`の特徴

- Excelで編集 &rarr; 保存 &rarr; `pd.read_csv`やその逆はもう絶対にやりたくない。スプレッドシートとJupyter QtConsoleを同時に開いて、シートの編集とPythonでの解析を素早く行き来できるようにした。
- Excelのxlsx重視とか勝手に日付に直してくるとかそういう変な機能全部なくした。
- VSCodeのコマンドパレット便利すぎる。実装した。
- Undo/redo (`Ctrl+Z`/`Ctrl+Y`) はすごく大変だけど、大事なので頑張って実装した。
- プロットは`matplotlib`と同じくできる一方で、インタラクティブにも動かせるようにした。
- カラムごとのデータ型があり、編集時に型チェックが入る`Table`型と、いわゆるスプレッドシートみたく自由に編集できる`SpreadSheet`型を用意した。
- 拡張・流用が簡単にできるようにした。自作Qtウィジェットをビューアーに追加することも、ビューアーやテーブルを自作Qtウィジェットに追加することも可能。

# インストール

pipでインストールできます。

```shell
$ pip install tabulous[all]
```

`seaborn`や`scikit-learn`とか、少し重めなのが必要なかったら、`PyQt`のバージョンだけ指定してインストールしてください。最低限のものだけインストールされます。

```shell
$ pip install tabulous[pyqt5]  # こっちの方が多分安定
$ pip install tabulous[pyqt6]
```

`tabulous`コマンドでビューアーを起動します。

```shell
$ tabulous
```

もしくはPythonを起動して、`TableViewer`オブジェクトを作成すると起動します。

```shell
$ python
Python 3.9.7 (...いろんな情報)
>>> from tabulous import TableViewer
>>> viewer = TableViewer()
```

Ctrl+Nを押すと新しいスプレッドシートが追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/d3306d6f-0fc8-50f0-0bdd-398c969d4b77.png)

このスプレッドシートはExcelみたく編集できます。

# 基本操作

以下、MacではCtrlを⌘など、適宜読み替えてください。

1. [QtConsoleの起動](#1-qtconsoleの起動)
2. [テーブルの追加/取得](#2-テーブルの追加取得)
3. [並び替え・フィルター](#3-並び替えフィルター)
4. [コマンドパレット](#4-コマンドパレット)
5. [カラーマップ](#5-カラーマップ)
6. [セル内での計算](#6-セル内での計算)
7. [プロットの編集](#7-プロットの編集)

### 1. QtConsoleの起動

Ctrl+Shift+Cを押すか、上のツールバーの"Home"ツールの">_"みたいなボタンを押すと、下部に[QtConsole](https://github.com/jupyter/qtconsole)ウィジェットが追加されます。もう一度押すと隠れます。基本的にここでPythonを対話形式で動かします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/7a04f133-23e2-901a-218b-92c9ec08eb1f.png)

なお、ビューアー自体は`viewer`という変数名で名前空間に追加されています。この`viewer`を使ってプログラム的にビューアー自体を操作していくことになります。

### 2. テーブルの追加/取得

ファイルを開く場合は、`Ctrl+O`で`Table`として、`Ctrl+K`&rarr;`Ctrl+O`で`SpreadSheet`として開きます。

一方、プログラム的には、`pd.DataFrame`に渡せるものであれば`add_spreadsheet`関数で追加できます。

```python
viewer.add_spreadsheet({"a": [1, 2, 3]})
```

テーブルは全て`viewer.tables`という`list`みたいなものに格納されています。テーブルやその内部の`pd.DataFrame`は次のように取得できます。

```python
table = viewer.tables[i]  # i番目のテーブルを取得
df = table.data  # テーブルデータをpd.DataFrameで取得
```

なお、現在アクティブなテーブルは`viewer.current_table`で取得可能です。

### 3. 並び替え・フィルター

テーブルデータの基本ですね。並び替え/フィルターはカラムを右クリックして"Sort"や"Filter"をクリックすればできます。

##### 並び替え

GUIではカラムを右クリックして"Sort"をクリックすればできます。

![メディア1-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/2bcc7184-fe9f-4be1-0668-a6ad361f6bdc.gif)

プログラム的には例えば次のようにやります。

```python
table.proxy.sort("A")
table.proxy.sort("A", ascending=False)
```

##### フィルター

GUIではカラムを右クリックして"Filter"をクリックすればできます。

![filter-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/f2f6db5e-8294-16dc-a5b0-e1dc71014fbf.gif)

プログラム的には例えば次のようにやります。

```python
table.proxy.filter("C == 'x'")
table.proxy.filter("A % 2 == 0")
```

##### 並び替え・フィルター中のセルの編集

できますよ～。

![edit-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/d4a77d29-a69e-7fbe-b90d-1a192207987b.gif)


### 4. コマンドパレット

「この操作がしたいけどどこにあるの...?」みたいな問題も、コマンドパレットがあればほとんど起きないですよね。コマンドパレットは`Ctrl+Shift+P`もしくは`F1`で起動します。

下の例は、散布図 &rarr; 集計 &rarr; 列の挿入 をコマンドパレットで実行したものです。

![palette-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/c4823c5c-0ca0-ffff-dc2e-b56eb56d5e8a.gif)

### 5. カラーマップ

GUIでは、カラムを右クリックしてテキスト・背景にカラーマップを設定できます。

![colormap-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/4cf4fce6-9800-0e9f-69ed-da934267ff44.gif)

プログラム的には、様々な方法があるので、[ドキュメント](https://hanjinliu.github.io/tabulous/main/columnwise_settings.html)を参考にしてください。

### 6. セル内での計算

Excelでの`=SUM(A1:A5)`みたいのものも（かなり頑張って）実装しました。`np`と`pd`が名前空間に存在し、さらにテーブルデータが`df`に渡されます。Excelみたく、選択領域に基づいて式が更新されます。

![eval-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/291a50a4-a5c9-c5c5-f5ed-1efe55d3258b.gif)

Excelと異なる点として、"="でスタートする場合は参照を残さず（参照元のセルの値が変わっても更新されない）、"&="でスタートする場合に参照を残すようにしています。これは、セルの値から何度も計算する操作はプログラム的に行う場合が多いので、GUI上では想定外の値の更新を防ごうと考えたためです。
（ただ、正直一般的な需要としてどうなのか分からないので、今後Excelみたいに"="で統一する可能性もありますね...）

### 7. プロットの編集

インタラクティブに拡大縮小・平行移動できる以外に、ダブルクリックでプロットした要素を編集できます。

![plot-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/6a9a7c1a-cbbb-cd83-3228-e7747e2403c5.gif)

# 個人的な好みで実装した機能

### 1. セルのラベル

セルの上に「平均」とかラベルするのマジで嫌い。"F2"でセル編集モードに入るみたく、"F3"でラベルを編集できるようにしました。ラベルはデータには含まれません。

![label-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/c2013086-bdba-7cdd-23a0-1f277f3f6018.gif)

### 2. 様々なコピー・ペースト

Markdown、sphinx、Notion、HTML、あとは`numpy`のprintの結果などを直接やりとりできたら何かと便利だと思い、実装しました。

![copy-output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1752357/e94677c7-0a42-6d98-cedf-5a889efa2dd3.gif)

ドキュメントを作成するのに早速使えました。

# 最後に

一応`scipy`や`scikit-learn`を活用した機能も実装したのですが、長くなるのでとりあえずここまで。
`tabulous`をよろしくお願いいたします～！