Linuxでopenpyxlを利用してExcelのワークシートを操作する

LibreOfficeは、Microsoft Officeと高い互換性を持つオフィスソフトである。Linuxのディストリビューションには、ほとんどインストールされている。Microsoft OfficeからLibreOfficeのWriter、Calc、Impressといったファイルを読み込むことができ、編集も可能になっている。その逆も同じように読み込みこんで編集することが可能である。

何も知らないと、そんなことはないと思うかもしれないが、Office365のオンライン版でやってみるとよくわかるはずだ。Calcで作成した表(.ods)をなんの問題もなく読み込むことができる。ただし、相互の独自の関数などはサポートされないことに注意する。

ただ高い互換性といっても100%ではないが、一般的なビジネス文書では互換性は100%といってもいいほど、レイアウトが崩れることがない。Calcにおいては「Option VBASupport 1」というオプションを先頭に記述すればVBAのコードを動かせるようになった。

今回は、Excelが使えなくてもLibreOffice Calcで、Pythonを使ったワークシートへのアクセスについてやってみることにする。なお、使用する形式は.xlsx形式なので、保存する際は.odsではないことに注意しよう。

openpyxlのインストール

Excelのファイル(xlsx/xlsm/xltx/xltm)ファイルを読み書きする、Pythonのライブラリとしてopenpyxlをインストールする。直接Excelをコントロールするものではなく、ファイルに対してアクセスするものなので、形式さえあっていれば問題がない。

pip3 install openpyxl

ほかにもあるが、とりあえずこれひとつで事足りる。openpyxlについて詳しく知りたければ、ドキュメントがあるので読んでみるといいだろう。英語だが、右クリックで日本語に翻訳してしまえばいい。サンプルコードも掲載されているので、使い方ぐらいは理解できるはずだ。

簡単なデモコード

LibreOffice Calcで空のファイルを作成しておく。その際、ワークシートSheet1の名前を「成績表」に変更する。ファイル名は適当でも構わないが、一応「sample.xlsx」としておこう。フォルダは/home/<ユーザー名>/Documentsとしておく。同じフォルダに、以下のコードを記述して、「demo.py」として保存する。

LibreOffice Calcで空のファイルを作るのが面倒であれば、ファイルの新規作成の箇所をコメントアウトして実行すればよい。

# openpyxlを使った簡単なデモ
import pandas as pd
import openpyxl
from openpyxl.styles.borders import Border, Side
from openpyxl.styles.fonts import Font
from openpyxl.styles.alignment import Alignment


# ワークシートに書き込むデータ
title_txt = 'アニマル・フォレスト中学校 - 3教科成績表'
data = [
    ['氏名', '国語', '数学', '英語'],
    ['うさぎさん', 95, 80, 85],
    ['きりんさん', 75, 100, 92],
    ['わにさん', 100, 74, 50],
    ['らいおんさん', 98, 83, 85],
    ['ねこさん', 100, 100, 100],
    ['平均点', '=average(B3:B7)', '=average(C3:C7)', '=average(D3:D7)']
]

# ファイルの新規作成
# wbook = openpyxl.Workbook()
# sheet = wbook.active
# sheet.title = '成績表'
# wbook.save('./sample.xlsx')

try:
    # ファイルを開く
    wbook = openpyxl.load_workbook('./sample.xlsx')
except FileNotFoundError as e:
    print(e)
    exit()

# ワークシートは国語
sheet = wbook['成績表']

# 書き込む範囲の指定
data_cell_range = sheet['A2':'D8']

# 表タイトルを書き込む
sheet['A1'].value = title_txt
sheet.merge_cells('A1:D1')      # セルの結合
sheet['A1'].alignment = Alignment(horizontal="center")  # 中央揃え

# 表データを書き込む
for i in range(len(data_cell_range)):
    for j in range(len(data_cell_range[0])):
        data_cell_range[i][j].value = data[i][j]

# 罫線を引く
line_style = Side(style='thin', color='000000')
border = Border(top=line_style, bottom=line_style, left=line_style, right=line_style)
for row_number in range(2, 9):
    for col_number in range(1, 5):
        sheet.cell(row=row_number, column=col_number).border = border

# 表のタイトル、教科名、平均点を太字にする
font_style = Font(name='Noto Sans CJK JP', size=10, bold=True)
sheet['A1'].font = font_style
sheet['A8'].font = font_style
sheet['A2'].font = font_style
sheet['B2'].font = font_style
sheet['C2'].font = font_style
sheet['D2'].font = font_style

# 上書き保存
wbook.save('./sample.xlsx')

# ワークシートの読み込みと内容を表示
df = pd.read_excel('./sample.xlsx')
for row in df.values:
    print(row[0], row[1], row[2], row[3])

# ワークブックを閉じる
wbook.close()
実行結果
実行結果

保存したsample.xlsxの内容をPandasで読み込んで表示した内容は下記のとおりである。タイトルは表示されていない。nanはデータが欠損つまりデータがないということを表す。平均点については、本来なら数式で計算されたものが入るはずだが、nanとなっている。xlwingsを使ってExcelを直接コントロールすれば表示できるようだが、残念ながらLibreOffice Calcではダメなようである。

最終的にはExcelでの確認なので、そこまでこだわらなければこのままでよいと思うが、平均点の結果を表示させたい場合は、数式ではなくあらかじめ平均点を求めてセルに書き込むか、表示時に平均点を求めるという方法もある。

氏名 国語 数学 英語
うさぎさん 95 80 85
きりんさん 75 100 92
わにさん 100 74 50
らいおんさん 98 83 85
ねこさん 100 100 100
平均点 nan nan nan

コメントをツケておいたので、リストを見れば何をしているか理解できるはずである。どういうふうにすればいいか理解できればOKである。

ためしに平均点を求めるコードを書いてみた。冗長なところもあるが、そこのところは勘弁いただきたい。

import openpyxl


# 各教科の合計
sum = [0, 0, 0]

try:
    wbook = openpyxl.load_workbook('./sample.xlsx')
except FileNotFoundError as e:
    print(e)
    exit()

sheet = wbook['成績表']
maxrow = sheet.max_row

# 表のタイトルと教科名の表示
print(sheet['A1'].value)
print()
print('%-6s        %2s   %2s   %2s' % (
    sheet['A2'].value,
    sheet['B2'].value,
    sheet['C2'].value,
    sheet['D2'].value)
)
print('----------------------------------')

# 各教科ごとの点数の集計
for i in range(3, maxrow):
    # 合計の計算
    sum[0] += sheet['B' + str(i)].value
    sum[1] += sheet['C' + str(i)].value
    sum[2] += sheet['D' + str(i)].value

    # 氏名の長さ調整
    s = sheet['A' + str(i)].value
    name = s.ljust(6, ' ')  # ' 'は全角空白1文字

    # 氏名と教科点数の表示
    print('%-6s     %3d    %3d    %3d' % (
        name,
        sheet['B' + str(i)].value,
        sheet['C' + str(i)].value,
        sheet['D' + str(i)].value)
    )

print('----------------------------------')

# 各教科の平均点の表示
print(sheet['B2'].value, ':', sum[0] / (maxrow - 3))
print(sheet['C2'].value, ':', sum[1] / (maxrow - 3))
print(sheet['D2'].value, ':', sum[2] / (maxrow - 3))

# ワークブックを閉じる
wbook.close()

(maxrow – 3)の部分については、表の行数が8行であるため、sheet.max_rowで8が返される。このうち、点数が入っている行数は5行なので−3としている。実行結果は以下のとおりである。

アニマル・フォレスト中学校 - 3教科成績表

氏名            国語   数学   英語
----------------------------------
うさぎさん       95     80     85
きりんさん       75    100     92
わにさん       100     74     50
らいおんさん      98     83     85
ねこさん       100    100    100
----------------------------------
国語 : 93.6
数学 : 87.4
英語 : 82.4