IronPythonを使ったExcel操作の覚え書き（後編）

[前編](https://qiita.com/Mt_SQ/items/6b17a26f75d252aaa83f)に続き，後編ではセル範囲の操作，そして.NETとの連携にも少しだけ触れます。
##日付データの扱い
Excelの日付データは.NETの[`System.DateTime`構造体](https://docs.microsoft.com/ja-jp/dotnet/api/system.datetime?view=netframework-4.8)（オブジェクト）です。
一方，Pythonは[`datetime`モジュール](https://docs.python.org/ja/2.7/library/datetime.html?highlight=datetime#module-datetime)で日付を扱います。
IronPythonはどちらの機能も使うことができます。
それぞれの機能の説明は割愛して，相互の変換のみ確認します。
例として，セルA1に「4/1」と入力します。通常は「4月1日」として日付データになります。
以下，確認の様子です。

```:IronPython&#x20対話モード画面
>>> from System import DateTime
>>> from datetime import *
>>> nd = ws.Range['A1']()    # Excelの日付データは.NETのSystem.DateTime
>>> nd
<System.DateTime object at 0x000000000000007F [2021/04/01 0:00:00]>
>>> pd = datetime(nd)    # Pythonのdatetimeオブジェクトに変換
>>> pd
datetime.datetime(2021, 4, 1, 0, 0)
>>> nd2 = DateTime.Parse(str(pd))    # Pythonから.NETへは文字列にしてから変換
>>> nd2
<System.DateTime object at 0x0000000000000080 [2021/04/01 0:00:00]>
>>> ws.Range['A2'] = nd2    # 変換後のデータをセルA2に貼り付けてみる
>>>
```
##セル範囲の操作（１行または１列のデータ）
セル範囲に対する`Value`プロパティで受け渡しされるデータは，.NETの２次元[`System.Array`クラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.array?view=netframework-4.8)（以下，`Array`）です。
`Array`クラスのインスタンス上にデータを用意して一気にExcelに貼り付けた方が処理が速くなります。

具体例を示します。

###(1) １行のデータ
####例１　リストを１行のセル範囲に書き込む
前編を参考に新規ワークブックを開きます。
前編同様，参照しているシートを`ws`とします。
リスト[0, 1, 2, 3, 4]をセルA1:E1の１行５列に書き込みます。
なお，セル範囲が１行の場合に限り，１次元の`Array`でも書き込めます。

```python:IronPython
from System import Array
lst = range(5)
ws.Range['A1:E1'] = Array[object](lst)
```
もちろん`ws.Range['A1:E1'] = Array[object](range(5))`でも同じです。
Pythonのリストやタプルは簡単に１次元`Array`に変換できるので，そのままセル範囲に書き込みます。

####例２　１行のセル範囲をリストにする
ここでは手順を追って確認してみます。

```:IronPython&#x20対話モード画面
>>> val_A1E1 = ws.Range['A1:E1']
>>> val_A1E1
<2 dimensional Array[object] at 0x000000000000006D>
```
セル範囲が１行でも，受け取る値は２次元`Array`です。

```:IronPython&#x20対話モード画面
>>> list(val_A1E1)
[0.0, 1.0, 2.0, 3.0, 4.0]
```
`Array`は`list`関数でPythonの１次元リストに変換できます。（`tuple`関数も同様）
###(2) １列のデータ
セル範囲が縦１列の場合，ワークシート関数`Transpose`を活用します。
####例３　１次元のリストを１列のセル範囲に書き込む
リスト[0, 1, 2, 3, 4]をセルA1:A5の５行１列に書き込みます。

```python:IronPython
tr = ex.WorksheetFunction.Transpose    # 別名をつけて短縮すると使いやすい（任意）
ws.Range['A1:A5'] = tr(Array[object](lst))    # １行のデータを１列に転置
```
####例４　１列のセル範囲をリストにする
例２と同じです。`list`関数は２次元`Array`でも１次元のリストに平坦化します。

```python:IronPython
val_A1A5 = ws.Range['A1:A5']()
list(val_A1A5)
```
###(3) Arrayクラスの扱い
`Array`クラスのデータを直接利用する場合は，プロパティやメソッドを使います。
先に取得した`val_A1A5`を使って内容を確認してみます。
Excelから取得した２次元`Array`の**ベースは[1,1]**です。（`Cells.Item`プロパティと同じイメージ）

```:IronPython&#x20対話モード画面
>>> val_A1A5.GetValue(4, 1)
3.0
```
IronPythonでは，`Array`クラスのデータをPythonのシーケンス同様に”識別子[インデックス]”で扱うこともできます。ただし，**ベースが[0,0]になる**ことに注意します。
よって，`val_A1A5.GetValue(4, 1)`は`val_A1A5[3, 0]`と同じ要素を指します。

```:IronPython&#x20対話モード画面
>>> val_A1A5[3, 0]
3.0
```
また，例2・例４で`list`関数に渡したように，`Array`クラスのデータはIronPythonにおいて**「反復可能なオブジェクト」**として扱われるので，２次元`Array`でも`for`文で直接回すことができます。
例示はしませんが，１次元`Array`の場合はスライスも使えます。
詳細は[公式ドキュメント](https://ironpython.net/documentation/dotnet/dotnet.html#net-arrays)をお読みください。

```python:IronPython
# ２次元Arrayを回す
for d in val_A1A5:
    print d
```
##セル範囲の操作（複数行×複数列のデータ）
最後に，複数行×複数列のセル範囲です。
とりあえずセルA1:E10の範囲にExcelの数式で適当にデータを入れます。

```python:IronPython
ws.Range['A1:E10'] = '=(COLUMN()-1)*10+ROW()'
```
###値の取得
単独セル同様に２次元`Array`で取得できます。

```python:IronPython
val_A1E10 = ws.Range['A1:E10']()    # ２次元Array
```
ただし，後の処理を考えると，取得したデータは２次元リストかタプルの方がPythonの機能を効率よく利用できます。
`Rows`や`Columns`プロパティと内包表記を組み合わせればPythonの２次元リスト（リストのリスト）が簡単に得られます。

```python:IronPython
list2R_A1E10 = [list(r()) for r in ws.Range['A1:E10'].Rows]    # ２次元リスト 行単位
list2C_A1E10 = [list(c()) for c in ws.Range['A1:E10'].Columns]    # ２次元リスト 列単位
```
`r`や`c`は`Range`オブジェクトなので，`r()`，`c()`だけで`Value`が取得できるのは便利です。（[前編参照](https://qiita.com/Mt_SQ/items/6b17a26f75d252aaa83f#%E4%BE%8B%EF%BC%93value%E3%81%AE%E7%9C%81%E7%95%A5)）

確認します。

```:IronPython&#x20対話モード画面
>>> list2R_A1E10
[[1.0, 11.0, 21.0, 31.0, 41.0], [2.0, 12.0, 22.0, 32.0, 42.0], [3.0, 13.0, 23.0, 33.0, 43.0], [4.0, 14.0, 24.0, 34.0, 44.0], [5.0, 15.0, 25.0, 35.0, 45.0], [6.0, 16.0, 26.0, 36.0, 46.0], [7.0, 17.0, 27.0, 37.0, 47.0], [8.0, 18.0, 28.0, 38.0, 48.0], [9.0, 19.0, 29.0, 39.0, 49.0], [10.0, 20.0, 30.0, 40.0, 50.0]]
>>> list2C_A1E10
[[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0], [11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0], [21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0], [31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0], [41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0]]
>>>
```
###値の設定
もし，PythonからExcelに貼り付けるデータがセル範囲をイメージした２次元リストであれば，２次元`Array`に変換して一括でExcelに貼り付けた方が高速です。VBAでも配列変数を使って一括貼り付けする方法が数多く見られます。

残念ながらPythonの２次元リストを２次元`Array`に直接変換する手段はありませんので，２次元リストの各要素を２次元`Array`に複写します。

先ほど取得した２次元リスト`list2R_A1E10`からすべてのデータを２倍にした２次元`Array`（`arr2D`）を作成して，セルH1:L10に書き込んでみます。

```python:IronPython
arr2D = Array.CreateInstance(object, 10, 5)    # 10行5列の２次元Arrayを作成
for r in range(10):
    for c in range(5):
        arr2D[r, c] = list2R_A1E10[r][c] * 2

ws.Range['H1'].Resize[10, 5] = arr2D    # 貼り付け（10行5列を意識してResizeを使用）
```
for文で回すことになりますが，`Array`上なので高速です。

なお，多重ループの場合，Pythonでは`itertools.product`がよく使われます。

```python:IronPython
from itertools import product
for r, c in product(range(10), range(5)):
    arr2D[r, c] = list2R_A1E10[r][c] * 2
```

ちなみに，２次元`Array`の作成は`Array.CreateInstance`が正攻法？ですが，私自身は，ExcelとIronPythonの画面を並べて，VBAの「イミディエイトウィンドウ」的に使うことが多いので，貼り付け先のセル範囲を使って
`arr2D = ws.Range['H1:L10']()`とか
Excel上で貼り付け先のセル範囲を選択して
`arr2D = ex.Selection()`
で横着することもあります。
##おわりに
本稿は「Excel作業中に，補助器具としてPythonの対話モードを使う」という前提でまとめました。
細かいことを書けばキリがないので，ここでは値の扱いだけに内容を絞っています。
手の込んだコレクション作業が多い場合はF#(fsi)，ちょっとした単純作業はIronPythonみたいな感じで使い分けていますが，本当に重宝します。

なお，この原稿を書いている最中に，職場で簡単な[実例](https://qiita.com/Mt_SQ/items/1e264ae245eb9a8d7d17)ができたので， ~~後日投稿する予定です。~~
（投稿しました。Excelを扱う記事ではありませんが，実務ではExcelと連携させています。）