#初めに
初投稿ですので、分かりにくい部分もあるかと思いますが、最後まで見ていただけたら幸いです。
pythonを勉強して1年くらいが経ちました。
最初こそは仕事でデータ解析目的でpythonを使用していましたが、データ分析のみならずpythonによる解析アプリを作る機会が増えてきました。その際にデータ分析で描いた大量のグラフをエクセルにまとめるのが大変でした。
私の周りにいる多くの人たちが大量のグラフまとめに苦心していることを知ったので、
共有と自分自身に対するメモとして、大量のグラフをエクセルにまとめる方法を投稿したいと思います。
#実行環境
Python 3.9
pandas 1.2.5
openpyxl 3.0.7
pycharm 2021.3
#Pythonサンプルコード
サンプルコード全体
import pandas as pd
import matplotlib.pyplot as plt
import openpyxl as px
import numpy as np
from pathlib import Path
import os
from openpyxl.drawing.image import Image
from openpyxl.utils import get_column_letter
#データを乱数で生成する
def gen_data():
return np.random.randn(100, 1)
#エクセルを開いて、画像をエクセルに貼る
def put_img_path(pth):
wb = px.load_workbook(pth)
ws = wb.worksheets[0]
#最大行数
max_row = ws.max_row
#最大列数
max_col = ws.max_column
for j in range(1, max_row):
row = j + 1
img_file = ws.cell(row=row, column=max_col).value
if not os.path.exists(img_file):
ws.cell(row=row, column=max_col).value = 'No image'
else:
img = Image(img_file)
#エクセルにあったパスを削除する
ws.cell(row=row, column=max_col).value = ''
aspect = img.width / img.height
img.width = 200
img.height = 200*aspect
#セルの幅、高さを画像に合わせて調整する
ws.row_dimensions[row].height = img.height
ws.column_dimensions[get_column_letter(max_col)].width = img.width * 0.25
#画像を指定するセルにはる
ws.add_image(img, ws.cell(row=row, column=max_col).coordinate)
wb.save(pth)
def main():
#このファイルがある場所を絶対パスで指定
base_dir = Path(__file__).resolve().parent
#画像ファイルを保存するファイルを作成する
img_path = base_dir / 'image'
os.makedirs(img_path, exist_ok=True)
#figを生成
fig = plt.figure(figsize=(4,4))
ax = fig.add_subplot(111)
#画像パスを収集するための空データフレーム
img_df = pd.DataFrame()
for i in range(100):
# clear
ax.cla()
y = gen_data()
label = i // 3
ax.plot(y)
ax.set_title(f'data_{i}')
ax.set_xlabel('x')
ax.set_ylabel('y')
img_data_path = f'{i}_img.jpg'
#画像データ保存
fig.savefig(img_path/img_data_path)
tmp = pd.DataFrame({'label':label, 'img':str(img_path/img_data_path)}, index=[i])
img_df = pd.concat([img_df, tmp])
#今まで画像パスを集計していたデータフレームをエクセルファイルとして保存する
excel_file = 'summary.xlsx'
img_df.to_excel(base_dir / excel_file, index=False)
put_img_path(base_dir / excel_file)
#解説
まず、エクセルに画像を貼るためには、openpyxl.drawing.image.Imageを読み込みます。
from openpyxl.drawing.image import Image
main関数では、平均値0、分散1の乱数をforループのたびに生成し、折れ線グラフ生成して、画像として保存しています。その際、forループ外で予め定義しておいた空データフレームimg_dfに保存した画像の絶対パスをpd.concatで1つずつ追加していきます。forループを抜けた後は、img_dfをエクセルファイルとして一旦保存します。
#画像パスを収集するための空データフレーム
img_df = pd.DataFrame()
for i in range(100):
# clear
ax.cla()
y = gen_data()
label = i // 3
ax.plot(y)
ax.set_title(f'data_{i}')
ax.set_xlabel('x')
ax.set_ylabel('y')
img_data_path = f'{i}_img.jpg'
#画像データ保存
fig.savefig(img_path/img_data_path)
tmp = pd.DataFrame({'label':label, 'img':str(img_path/img_data_path)}, index=[i])
img_df = pd.concat([img_df, tmp])
#今まで画像パスを集計していたデータフレームをエクセルファイルとして保存する
excel_file = 'summary.xlsx'
img_df.to_excel(base_dir / excel_file, index=False)
エクセルに画像を貼っていく処理は、関数put_img_pathとして定義しています。引数として画像パスがまとめてあるエクセルファイルの名前もしくはファイルパスを受け取ります。forループで画像をエクセル内のファイルパスを元に、
画像を貼っていきますが、その前に、最大行数と最大列数を取得しておきます。
そして、forループで画像パスを1セルずつ読み、Image(img_file)によってエクセルに貼る形式に画像を読み込みます。
その後画像を貼るのですが、画像によってサイズが若干異なるといった事態を想定して、画像サイズを均一に調整しています。
このまま画像を貼りたいところですが、セルの幅が狭すぎて互いの画像が被るため、後で手作業でセルのサイズを調整する手間が発生してしまうため、row_dimensionsとcolumn_dimensionsを使用して、セルサイズを画像に合わせて調整します。
最後にadd_imageで画像をエクセルに貼ります。その際の指定の仕方ですがcoordinateメソッドでセル番地(A1といった形式)を指定しておきます。
def put_img_path(pth):
wb = px.load_workbook(pth)
ws = wb.worksheets[0]
#最大行数
max_row = ws.max_row
#最大列数
max_col = ws.max_column
for j in range(1, max_row):
row = j + 1
img_file = ws.cell(row=row, column=max_col).value
if not os.path.exists(img_file):
ws.cell(row=row, column=max_col).value = 'No image'
else:
img = Image(img_file)
#エクセルにあったパスを削除する
ws.cell(row=row, column=max_col).value = ''
aspect = img.width / img.height
img.width = 200
img.height = 200*aspect
#セルの幅、高さを画像に合わせて調整する
ws.row_dimensions[row].height = img.height
ws.column_dimensions[get_column_letter(max_col)].width = img.width * 0.25
#画像を指定するセルにはる
ws.add_image(img, ws.cell(row=row, column=max_col).coordinate)
wb.save(pth)
#まとめ
今回はpythonを使って大量のグラフをエクセルにまとめる方法について書いていきました。説明を簡略化するためにサンプルコードもシンプルにしています。
少しでもお役に立てれれば幸いです。