0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Matplotlib: 表を作る(2) Excelのデータを読み取り表形式で画像出力

Last updated at Posted at 2025-08-31

はじめに

エクセルで作った表を TeX や html などに貼り付けいたい場合、どうしているだろうか?
簡単なのは、エクセルを起動してスクリーンショットを撮って貼り付けてしまう、エクセルの表を pdf 出力し、これを Preview などで加工する方法だと思う。
しかしながら、表の中身が変わったり、たくさんの表の処理を行う場合など、何度も同じ作業をやらなければならないので、面倒である。
そこで、Python プログラムにより、エクセルのデータを読み取って表形式で画像出力する方法を考えた。
どうしてもエクセルの画像じゃないと嫌だという人には役に立たないが、私のニーズは数値を表形式で示せればいいので、このような方法は有益である。
表形式出力の方法として、matplotlib.table を使う方法と、自力で patches.Rectangle の中に text データを書き込む方法を試してみた。
当方の環境は以下の通り。

M1 Pro MacBook Pro 14"
Python 3.17.7
matplotlib 3.10.5
openpyxl 3.1.5

作成

まずは作例を示そう。

(1)エクセルの表(スクリーンショット)

ここからデータを読み取り、表形式で画像出力する。
Screenshot 2025-08-30 at 17.00.16.png

(2)matplotlib.table

fig_test1.jpg

(3)Rectangle + text

fig_test2.jpg

解説

inp_excel

  • エクセルの表からデータを抽出し2次元配列に格納する。
  • データの大きさが変わることを想定している。
  • 行方向はA列を検索し、空セルを3つ数えたらその直前までを読み取り範囲とする。
  • 列方向は2行めを検索し、空セルを1つ数えたらその直前までを読み取り範囲とする。
  • データは空セルも含めて指定した範囲内にあるものはすべて読み込む。
  • 後で数値計算用データとしても使えるよう、空セルは空文字列、文字列は文字列、数値は数値(ここでは小数点3位まで)として2次元配列に格納する。
  • 数値であるかの判定は、float を当ててtry... except... で処理する方法が信頼できるようである。

make_sdata

表として出力するため、数値を文字列に変換し、全要素を文字列とした配列を作成。

mp_tbl

  • matplotlib.tableで表を表示。
  • 全要素を文字列に変換した2次元配列をtableに投げ込む。
  • 表の表示においてはedges='open'として、ここでは罫線の表示は行わない。
  • bboxで表の大きさを指定。
  • tblというtableオブジェクトを作成後、tblに対して装飾を施していく。
  • tbl.auto_set_font_size(False), tbl.set_fontsize(fsz)でフォントサイズを指定
  • tbl[i,j].set_text_props(ha='left')で指定箇所表示を左寄せに指定
  • tbl[i,j].visible_edges='closed'で指定せるの罫線による取り囲みを指定
  • tbl[i,j].set_edgecolor('#000000')で罫線色を黒に指定
  • tbl[i,j].set_linewidth(0.5)で罫線はばを0.5に指定
  • tbl[i,j].set_facecolor('#dddddd')で指定箇所を灰色で塗りつぶし

my_tbl

patches.Rectangle + textで表を表示。
説明は省略。

fig

グラフの全体像の定義

  • data=inp_excel(fnameR) エクセルデータ取得()
  • sdata=make_sdata(data) エクセルデータは、文字列は文字列、数値は数値として読み込まれているので、数値を必要桁に揃えた文字列に変換している。
  • tw,th=1,1で表の幅と高さ定義。
  • ```cおl```及びcolwという配列で列幅を定義。
  • ここではグラフの描画範囲をx=(0,1)y=(0,1)で定義し、表もこの領域全体に表示するようにしている。
  • 一般的なグラフ作成の手続きを踏んで、plt.axis('off')で軸を非表示にする。
  • あとは見ての通りかな。

main

2つのケースの制御

プログラム

import openpyxl as excel
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np


def inp_excel(fnameR):
    book=excel.load_workbook(fnameR,data_only=True)
    worksheet=book['data']
    srmax,scmax=50,10 # search range
    k=0
    sr=[]
    for i in range(0,srmax):
        nrow=i+1
        ss=worksheet.cell(row=nrow,column=1).value
        if ss==None: k=k+1
        if k<3: sr.append(ss)
        if k==3: break
    n=len(sr)
    k=0
    sc=[]
    for j in range(0,scmax):
        ncol=j+1
        ss=worksheet.cell(row=2,column=ncol).value
        if ss==None: k=k+1
        if k<1: sc.append(ss)
        if k==1: break
    m=len(sc)
    rstr=f'A1:{chr(64+m)}{n}'
    rect=worksheet[f'{rstr}']
    data=[]
    for row in rect:
        row_data=[]
        for cell in row:
            s=cell.value
            if s==None: s1=''
            else:
                try:
                    s=float(s)
                    s1=np.round(s,decimals=3)
                except ValueError:
                    s1=s
            row_data.append(s1)
        data.append(row_data)    
    data=np.array(data,dtype=object)
    return data


def make_sdata(data):
    # change to str data
    n,m=data.shape
    sdata=np.empty((n,m),dtype=object)
    for i in range(n):
        for j in range(m):
            s=data[i,j]
            if s=='': s1=''
            else:
                try:
                    s=float(s)
                    s1=f'{s:.3f}'
                except ValueError:
                    s1=s
            sdata[i,j]=s1
    return sdata


def mp_tbl(sdata,trow,dparam,fsz):
    # draw table using matplotlib.table
    xmin,xmax=dparam[0],dparam[1]
    ymin,ymax=dparam[2],dparam[3]
    tw,th=dparam[4],dparam[5]
    colw=dparam[6]    

    tbl=plt.table(
        cellText=sdata,
        colWidths=colw,
        cellLoc='center',
        edges='open',
        bbox=[xmin,ymin,tw,th]
    )

    tbl.auto_set_font_size(False)
    tbl.set_fontsize(fsz)
    n,m=sdata.shape
    for i in range(n):
        for j in range(m):
            if i==trow[0] or i==trow[1] or i==trow[2]:
                tbl[i,j].set_text_props(ha='left')
            if i!=trow[0] and i!=trow[1] and i!=trow[2] and sdata[i,j]!='':
                tbl[i,j].visible_edges='closed'
                tbl[i,j].set_edgecolor('#000000')
                tbl[i,j].set_linewidth(0.5)
                if i==trow[0]+1 or i==trow[1]+1 or i==trow[2]+2:
                    tbl[i,j].set_facecolor('#dddddd')


def my_tbl(sdata,trow,dparam,fsz):
    # draw table using rectangle
    xmin,xmax=dparam[0],dparam[1]
    ymin,ymax=dparam[2],dparam[3]
    tw,th=dparam[4],dparam[5]
    colw=dparam[6]    

    n,m=sdata.shape
    dh=th/n
    for i in range(n):
        for j in range(m):
            if sdata[i,j]=='': continue
            col='#ffffff'
            if i==trow[0]+1 or i==trow[1]+1 or i==trow[2]+2:
                col='#dddddd'
            x0=xmin+np.sum(colw[0:j])
            y0=ymax-dh*(i+1)
            if j==0: x0=xmin
            r=patches.Rectangle(
                xy=(x0,y0), 
                width=colw[j], 
                height=dh, 
                edgecolor='#000000', 
                facecolor=col,
                linewidth=0.5,
                clip_on=False
            )
            if i==trow[0] or i==trow[1] or i==trow[2]:
                hha,vva='left','center'
                xs=x0+0.1*colw[j]
            else:
                plt.gca().add_patch(r)
                hha,vva='center','center'
                xs=x0+0.5*colw[j]
            ys=y0+0.5*dh
            plt.text(xs,ys,sdata[i,j],ha=hha,va=vva,fontsize=fsz)


def fig(nnn,fnameR,fnameF):
    data=inp_excel(fnameR)
    sdata=make_sdata(data)
    n,m=sdata.shape
    trow=[]
    for i in range(n):
        if sdata[i,0]=='Data for basic design': trow.append(i)
        if sdata[i,0]=='Data for surging analysis': trow.append(i)
        if sdata[i,0]=='Shaft data': trow.append(i)
    i,j=trow[2]+1,2; sdata[i,j]=f'{data[i,j]:.0f}'
    print(sdata)

    tstr='Surging analysis data'
    tw,th=1,1
    col=np.array([2.0, 0.8, 0.8]+[1]*(m-3))
    colw=col/np.sum(col)*tw

    fsz=7.5
    xmin,xmax=0,1
    ymin,ymax=0,1    
    iw,ih=10, 12 # default (inch)
    plt.figure(figsize=(iw,ih),facecolor='w')
    plt.xlim([xmin,xmax])
    plt.ylim([ymin,ymax])
    plt.axis('off')

    dparam=[xmin,xmax,ymin,ymax,tw,th,colw]
    if nnn==1: mp_tbl(sdata,trow,dparam,fsz)
    if nnn==2: my_tbl(sdata,trow,dparam,fsz)

    if nnn==1: astr=' (matplotlib.table)'
    if nnn==2: astr=' (Rectangle + text)'
    tstr=tstr+astr
    plt.title(tstr,x=np.sum(colw[0:m])/2,fontsize=fsz+2,weight='bold')
    
    plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
    #plt.show()


def main():
    #fnameR='surge_data_TR.xlsx'
    #fnameR='surge_data_HR_1.xlsx'
    fnameR='surge_data_HR_2.xlsx'
    for iii in [1,2]:
        if iii==1: fnameF='fig_test1.jpg'
        if iii==2: fnameF='fig_test2.jpg'
        fig(iii,fnameR,fnameF)


#---------------
# Execute
#---------------
if __name__ == '__main__': main()

感想

matplotlib.tableもいじっていたら、だいぶ癖がわかってきたというか、自由に使えるようになってきた気がする。
どうしても上手くいかないときは、Rectangle + textという手もあるので、色々経験していこうと思う。

以 上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?