openpyxlでpythonからエクセルファイルを操作する

## OpenPyXLとは


pythonでエクセルファイル（xlsx, xlsm）を操作するパッケージです
https://openpyxl.readthedocs.io/en/stable/index.html

値をいじるだけじゃなく書式やテキストボックス、画像オブジェクトなどの要素を変更したり出来ますので、一般ユーザー向けに結果をEXCELファイルで書き出す際に書式を整えたりするのに便利です

公式チュートリアルは以下になります
https://openpyxl.readthedocs.io/en/stable/index.html


## 1. インストール


pipで入ります


```terminal:terminal
pip install openpyxl
```


画像ファイルを扱いたい場合はpillowも要ります


```terminal:terminal
pip install pillow
```


## 2. bookの操作


openpyxl.workbook.workbook.Workbookクラスのインスタンスとして扱われます


### 2-1. 新規作成


以下を実行すると空のワークブックを作成します


```python
>>> import openpyxl as xl
>>> wb = xl.Workbook()
>>> wb

<openpyxl.workbook.workbook.Workbook at 0x1a29558e670>
```


### 2-2. 保存


wbオブジェクトのsaveメソッドでファイルに書き出すことが出来ます


```python
wb.save('./book.xlsx')
```


### 2-3. 読み込み


ファイルからの読み込みも出来ます


```python
>>> wb2 = xl.load_workbook('./book.xlsx')
>>> wb2

<openpyxl.workbook.workbook.Workbook at 0x20388537e50>
```


## 3. sheetの操作


openpyxl.worksheet.worksheet.Worksheetクラスのインスタンスとして扱われます


### 3-1. アクティブシートの取得


```python
>>> ws = wb.active
>>> ws

<Worksheet "Sheet">
```


### 3-2. シート一覧の取得


ワークシートを左から順に入れたlistを受け取れます


```python:シートオブジェクトとして取得
>>> wb.worksheets

[<Worksheet "Sheet">]
```


```python:シート名のみ取得
>>> wb.sheetnames

['Sheet']
```


シートを１つずつ処理する場合は以下のように書くことが出来ます


```python:シートを順番に取り出す
>>> for sheet in wb:
>>>     print(sheet)

<Worksheet "Sheet">
```


### 3-3. シート名を変更する


```python
>>> ws.title = 'hoge'
>>> ws

<Worksheet "hoge">
```


### 3-4. シートタブの色を変更する


RGB16進数で指定できます


```python
>>> ws.sheet_properties.tabColor = "1072BA"
>>> ws.sheet_properties.tabColor

<openpyxl.styles.colors.Color object>
Parameters:
rgb='001072BA', indexed=None, auto=None, theme=None, tint=0.0, type='rgb'
```


### 3-5. シートを複製する


コピー対象のワークシートの右側にコピーしたワークシートを追加できます


```python
>>> ws2 = wb.copy_worksheet(ws)
>>> wb.worksheets

[<Worksheet "hoge">, 
 <Worksheet "hoge Copy">]
```


### 3-6. シートを追加する


wb.create_sheet(シート名, 追加位置)でワークシートを追加できます


```python:ワークシートを左端に追加する
>>> ws3 = wb.create_sheet("sheet3", 0)
>>> wb.worksheets

[<Worksheet "sheet3">, 
 <Worksheet "hoge">, 
 <Worksheet "hoge Copy">]
```


```python:挿入位置を指定してワークシートを追加する
>>> ws4 = wb.create_sheet("sheet4", 1)
>>> wb.worksheets

[<Worksheet "sheet3">,
 <Worksheet "sheet4">,
 <Worksheet "hoge">,
 <Worksheet "hoge Copy">]
```


追加位置を省略すると右端に追加するという指定になります


```python:シートを右端に追加する
>>> ws5 = wb.create_sheet("sheet5")
>>> wb.worksheets

[<Worksheet "sheet3">,
 <Worksheet "sheet4">,
 <Worksheet "hoge">,
 <Worksheet "hoge Copy">,
 <Worksheet "sheet5">]
```


右から何番目という指定もできます


```python:右から3番目に挿入する
>>> ws6 = wb.create_sheet("sheet6", -2)
>>> wb.worksheets

[<Worksheet "sheet3">,
 <Worksheet "sheet4">,
 <Worksheet "hoge">,
 <Worksheet "sheet6">,
 <Worksheet "hoge Copy">,
 <Worksheet "sheet5">]
​```


### 3-8. シートを移動する


```python
>>> wb.move_sheet(ws, offset=-1)
>>> wb.worksheets

[<Worksheet "sheet3">,
 <Worksheet "hoge">,
 <Worksheet "sheet4">,
 <Worksheet "sheet6">,
 <Worksheet "hoge Copy">,
 <Worksheet "sheet5">]
```


### 3-9. シートを削除する


```python
>>> wb.remove(ws)
>>> wb.worksheets

[<Worksheet "sheet3">,
 <Worksheet "sheet4">,
 <Worksheet "sheet6">,
 <Worksheet "hoge Copy">,
 <Worksheet "sheet5">]
```


## 4. セルの操作


### 4-1. セルの取得


```python:R1C1で指定する
>>> wb = xl.Workbook()
>>> ws = wb.active
>>> c1 = ws['A4']
>>> c1

<Cell 'Sheet'.A4>
```


```python:行、列の番号で指定する
>>> c2 = ws.cell(row=4, column=1)
>>> c2

<Cell 'Sheet'.A4>
```


### 4-2. 値の取得


セルのvalueプロパティに値が入っています
値が入っていない場合はNoneが返ってきます


```python:R1C1で指定する
>>> ws['A4'].value
```


```python:行、列の番号で指定する
>>> ws.cell(row=4, column=1).value
```


以下のように書いても同じです


```python:R1C1で指定して取得してから値を参照する
>>> c1 = ws['A4']
>>> c1.value
```


```python:行、列の番号で指定して取得してから値を参照する
>>> c2 = ws.cell(row=4, column=1).value
>>> c2.value
```


### 4-3. 値の変更


```python:R1C1で指定する
>>> ws['A4'].value = 3.14
>>> ws['A4'].value

3.14
```


```python:行、列の番号で指定する
>>> ws.cell(row=4, column=1).value = 6.28
>>> ws.cell(row=4, column=1).value

6.28
```


ワークシートのcellメソッドで値を入力することもできます


```python:行、列の番号で指定する
>>> c2 = ws.cell(row=4, column=1, value=6.28)
>>> c2.value

6.28
```


### 4-4. 数式の書き込み


文字列で書き込めば数式として扱ってくれるようです


```python
>>> ws['A4'].value = '= 2 ^ 8'
>>> print(ws['A4'].value, type(ws['A4'].value))

'= 2 ^ 8' <class 'str'>
```

文字列で入ってますが書き出すときにセル関数に変換してくれます
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/666a387c-139e-dab8-5405-784b5c3d0d3d.png" width=70%>
openpyxlは数式が正しいかチェックしてくれないようですが、セル関数が存在するかは以下のように確認することが出来るようです


```python
>>> from openpyxl.utils import FORMULAE
>>> "HEX2DEC" in FORMULAE

True
```


### 4-5. 書式の取得


#### number format


```python
import datetime
>>> ws['A1'] = datetime.datetime(2010, 7, 21)
>>> ws['A1'].number_format

'yyyy-mm-dd h:mm:ss'
```


### 4-6. 書式の変更


#### 文字方向を回転する


```python
ws['B2'].alignment = xl.styles.alignment.Alignment(textRotation=90)
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/9a9c3751-820c-41cd-7169-d82fc3638e96.png)


#### 背景色を変更する


```python
ws['B2'].fill = xl.styles.PatternFill(patternType='solid', fgColor='FFAAAA', bgColor='FFAAAA')
wb.save('./book.xlsx')
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/8d6ba61b-3829-2c1e-d19e-fabfb69c2bb0.png)


#### 文字色を変更する


```python
ws['D3'].font = openpyxl.styles.fonts.Font(color='0000FF', bold=True)
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/b9e9a7c1-76f9-aea1-4290-e85ea6c32409.png)



### 4-7. セルの結合


#### 結合


```python:R1C1で指定
ws.merge_cells('A2:D2')
```


```python:行、列の番号で指定
ws.merge_cells(start_row=2, start_column=1, end_row=4, end_column=4)
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/824e10ee-f27a-edd4-c95c-b93104954fb5.png)


#### 結合の解除


```python:R1C1で指定
ws.unmerge_cells('A2:D2')
```


```python:行、列の番号で指定
ws.unmerge_cells(start_row=2, start_column=1, end_row=4, end_column=4)
```


### 4-8. セルに画像を貼り付ける


```python
img = xl.drawing.image.Image('./sample.jpg')
ws.add_image(img, 'C3')
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/35dc490f-8b9a-7e64-95a0-ef3539244609.png)



## 5. 範囲の操作


### 5-1. 範囲の取得


#### 範囲で取得


範囲で取得するとセルオブジェクトをtupleに入れたものを取得できます


```python:R1C1で指定
>>> cell_range = ws['A1':'C2']
>>> cell_range

((<Cell 'Sheet'.A1>, <Cell 'Sheet'.B1>, <Cell 'Sheet'.C1>),
 (<Cell 'Sheet'.A2>, <Cell 'Sheet'.B2>, <Cell 'Sheet'.C2>))
```


```python:行、列の番号で指定
>>> cell_range = tuple(ws.iter_rows(min_row=1, max_col=3, max_row=2))

((<Cell 'hoge'.A1>, <Cell 'hoge'.B1>, <Cell 'hoge'.C1>),
 (<Cell 'hoge'.A2>, <Cell 'hoge'.B2>, <Cell 'hoge'.C2>))
```


列、行の順で取得することもできます


```python:列、行の順番で取得
>>> tuple(ws.iter_cols(min_row=1, max_col=3, max_row=2))

((<Cell 'Sheet'.A1>, <Cell 'Sheet'.A2>),
 (<Cell 'Sheet'.B1>, <Cell 'Sheet'.B2>),
 (<Cell 'Sheet'.C1>, <Cell 'Sheet'.C2>))
```


#### 行を選んで取得


```python:10行目を選択
>>> row10 = ws[10]
>>> row10

(<Cell 'Sheet'.A10>, 
 <Cell 'Sheet'.B10>, 
 <Cell 'Sheet'.C10>)
```


行全体を選択しているのに3列分しか取得されていないのは、現時点でワークシート内に3列分しかセルの情報がないからです


```python:5-10行目を範囲選択
>>> row_range = ws[5:10]
>>> row_range

((<Cell 'hoge'.A5>, <Cell 'hoge'.B5>, <Cell 'hoge'.C5>, <Cell 'hoge'.D5>),
 (<Cell 'hoge'.A6>, <Cell 'hoge'.B6>, <Cell 'hoge'.C6>, <Cell 'hoge'.D6>),
 (<Cell 'hoge'.A7>, <Cell 'hoge'.B7>, <Cell 'hoge'.C7>, <Cell 'hoge'.D7>),
 (<Cell 'hoge'.A8>, <Cell 'hoge'.B8>, <Cell 'hoge'.C8>, <Cell 'hoge'.D8>),
 (<Cell 'hoge'.A9>, <Cell 'hoge'.B9>, <Cell 'hoge'.C9>, <Cell 'hoge'.D9>),
 (<Cell 'hoge'.A10>, <Cell 'hoge'.B10>, <Cell 'hoge'.C10>, <Cell 'hoge'.D10>))
```


```python:すべての行を選択
>>> ws = wb.active
>>> tuple(ws.rows)

((<Cell 'Sheet'.A1>, <Cell 'Sheet'.B1>, <Cell 'Sheet'.C1>, <Cell 'Sheet'.D1>),
 (<Cell 'Sheet'.A2>, <Cell 'Sheet'.B2>, <Cell 'Sheet'.C2>, <Cell 'Sheet'.D2>),
 (<Cell 'Sheet'.A3>, <Cell 'Sheet'.B3>, <Cell 'Sheet'.C3>, <Cell 'Sheet'.D3>),
 (<Cell 'Sheet'.A4>, <Cell 'Sheet'.B4>, <Cell 'Sheet'.C4>, <Cell 'Sheet'.D4>),
 (<Cell 'Sheet'.A5>, <Cell 'Sheet'.B5>, <Cell 'Sheet'.C5>, <Cell 'Sheet'.D5>),
 (<Cell 'Sheet'.A6>, <Cell 'Sheet'.B6>, <Cell 'Sheet'.C6>, <Cell 'Sheet'.D6>),
 (<Cell 'Sheet'.A7>, <Cell 'Sheet'.B7>, <Cell 'Sheet'.C7>, <Cell 'Sheet'.D7>),
 (<Cell 'Sheet'.A8>, <Cell 'Sheet'.B8>, <Cell 'Sheet'.C8>, <Cell 'Sheet'.D8>),
 (<Cell 'Sheet'.A9>, <Cell 'Sheet'.B9>, <Cell 'Sheet'.C9>, <Cell 'Sheet'.D9>),
 (<Cell 'Sheet'.A10>,
  <Cell 'Sheet'.B10>,
  <Cell 'Sheet'.C10>,
  <Cell 'Sheet'.D10>))
```


#### 列を選んで取得


```python:C列を選択
>>> colC = ws['C']
>>> colC

(<Cell 'Sheet'.C1>, <Cell 'Sheet'.C2>)
```


```python:C-D列を選択
>>> col_range = ws['C:D']
>>> col_range

((<Cell 'Sheet'.C1>, <Cell 'Sheet'.C2>),
 (<Cell 'Sheet'.D1>, <Cell 'Sheet'.D2>))
```


```python:すべての列を選択
>>> tuple(ws.columns)

((<Cell '左に足す'.A1>,
  <Cell '左に足す'.A2>,
  <Cell '左に足す'.A3>,
  <Cell '左に足す'.A4>,
  <Cell '左に足す'.A5>,
  <Cell '左に足す'.A6>,
  <Cell '左に足す'.A7>,
  <Cell '左に足す'.A8>,
  <Cell '左に足す'.A9>),
 (<Cell '左に足す'.B1>,
  <Cell '左に足す'.B2>,
  <Cell '左に足す'.B3>,
  <Cell '左に足す'.B4>,
  <Cell '左に足す'.B5>,
  <Cell '左に足す'.B6>,
  <Cell '左に足す'.B7>,
  <Cell '左に足す'.B8>,
  <Cell '左に足す'.B9>),
 (<Cell '左に足す'.C1>,
  <Cell '左に足す'.C2>,
  <Cell '左に足す'.C3>,
  <Cell '左に足す'.C4>,
  <Cell '左に足す'.C5>,
  <Cell '左に足す'.C6>,
  <Cell '左に足す'.C7>,
  <Cell '左に足す'.C8>,
  <Cell '左に足す'.C9>))
```



### 5-2. 値の取得


空のセルの場合はNoneが返ってきます


```python:シート全体の値を取得する
>>> wb = xl.Workbook()
>>> ws = wb.active
>>> ws.values

((None, None, None),
 (None, None, None),
 (None, None, None),
 (None, None, None),
 (None, None, None),
 (None, None, None),
 (None, None, None),
 (None, None, None),
 (None, None, None))
```


範囲で取得した場合はtupleにセルオブジェクトをまとめてあるだけなので、ws['A1':'C2'].values のような書き方はできません


```python
>>> wb = xl.Workbook()
>>> ws = wb.active
>>> 
>>> cell_range = ws['A1':'C2']
>>> for row in cell_range:
>>>     for cell in row:
>>>         print(cell, cell.value)

<Cell 'Sheet'.A1> None
<Cell 'Sheet'.B1> None
<Cell 'Sheet'.C1> None
<Cell 'Sheet'.A2> None
<Cell 'Sheet'.B2> None
<Cell 'Sheet'.C2> None
```


### 5-3. 範囲内のセルの変更


```python:セル１つずつ書き換える場合
>>> wb = xl.Workbook()
>>> ws = wb.active
>>> 
>>> cell_range = ws['A1':'C2']
>>> for i, row in enumerate(cell_range):
>>>     for j, cell in enumerate(row):
>>>         cell.value = '{}-{}'.format(i, j)        
>>> tuple(ws.values)

<Cell 'Sheet'.A1> 0-0
<Cell 'Sheet'.B1> 0-1
<Cell 'Sheet'.C1> 0-2
<Cell 'Sheet'.A2> 1-0
<Cell 'Sheet'.B2> 1-1
<Cell 'Sheet'.C2> 1-2
(('0-0', '0-1', '0-2'), 
 ('1-0', '1-1', '1-2'))
```


ws.append()で書いていくとまだ何も書かれていない新しい行に1行ずつ書き足していくことが出来ます


```python:1行ずつ書き足す場合
>>> wb = xl.Workbook()
>>> ws = wb.active
>>> ws.append([1, 2, 3])
>>> ws.append([2, 3, 4])
>>> wb.save('./book.xlsx')
>>> tuple(ws.values)

((1, 2, 3), 
 (2, 3, 4))
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/f255c3ec-8710-6f9c-ad70-3e7d4bbd4ff6.png" width=50%>


## 6. pandasの利用


### 6-1. pandas.DataFrameの内容をワークシートに貼り付ける


pandas.DataFrameからワークシートをつくる機能が一応用意されています


```python
df = pd.DataFrame({'a': [1, 2, 3], 'b': [2, 3, 4]})        
df
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/bf80bac4-3d65-3c65-ca1e-a287ad9c40a3.png)
dataframe_to_rows()にpandas.DataFrameを渡すと1行単位で値を出してくれるイタレータをつくってくれます。これをワークシートオブジェクトにappend()していけばDataFrameの内容をシートに書き写すことが出来ます


```python
>>> from openpyxl.utils.dataframe import dataframe_to_rows
>>> 
>>> wb = xl.Workbook()
>>> ws = wb.active
>>> 
>>> rows = dataframe_to_rows(df, index=True, header=True)
>>> for k, row in enumerate(rows):
>>>     print(k, row, type(row))
>>>     ws.append(row)
>>>     
>>> wb.save('./book.xlsx')

0 [None, 'a', 'b'] <class 'list'>
1 [None] <class 'pandas.core.indexes.frozen.FrozenList'>
2 [0, 1, 2] <class 'list'>
3 [1, 2, 3] <class 'list'>
4 [2, 3, 4] <class 'list'>
```


上記はチュートリアルのコードそのままですが、これだと余計なのが出てきて1行空白が出来てしまうので、typeを確認して要らないものが出てきたらappend()しないように変更した方が良さげです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/bed55031-7f05-8148-69f5-d1b32b09c607.png)


### 6-2. ワークシートの値からpandas.DataFrameをつくる


ws.valuesでワークシート全体の値を取得すると2次元配列で取得できるので比較的スムーズに変換できます


```python
from itertools import islice
data = ws.values
cols = next(data)[1:]
data = list(data)
idx = [r[0] for r in data]
data = (islice(r, 1, None) for r in data)
df = pd.DataFrame(data, index=idx, columns=cols)
```


## 7. 行、列の操作


### 7-1. 行幅の変更


行幅はfloatで渡さないと無視されます


```python
ws.row_dimensions[3].height = 30.
```


### 7-2. 列幅の変更


列幅もfloatで渡さないと無視されます


```python
ws.column_dimensions['B'].width = 30.
```


### 7-3. 行、列をfoldして非表示にする


以下のようにすることで、A～D列、1～10行が非表示になります


```python
ws.column_dimensions.group('A', 'D', hidden=True)
ws.row_dimensions.group(1, 10, hidden=True)
```


hidden=Falseにすると表示されるようになります


```python
ws.column_dimensions.group('A', 'D', hidden=False)
ws.row_dimensions.group(1, 10, hidden=False)
```


### 8. 巨大なファイルを扱う場合


Read-only mode、Write-only modeを切り替えることでメモリ消費量を抑えて巨大なファイルを扱うことが可能になるようです
https://openpyxl.readthedocs.io/en/stable/optimized.html


### 9. グラフを操作する


たくさんあり過ぎて読む気がなくなったのでチュートリアルページのリンクだけ貼っておきます
https://openpyxl.readthedocs.io/en/stable/charts/introduction.html


### まとめ


正直、EXCELファイルの操作自体はVBAと比べて特別に書きやすいということはないと思いますが、EXCELファイルの操作以外の部分ではpythonの方が圧倒的に書きやすいので、EXCELから操作する必要がないのであれば python + openpyxl で書けることで得られるメリットはすごく大きいと思います。

レッツトライ