はじめに
昨年のアドベントカレンダーで、『MATLABにファイルデータを書き出す関数』を投稿したわけですが、早いもので、もう1年経ちました。
本記事は、その続編で、EXCELに書き出すことに特化してまとめてあります。一部、重複があるかもしれません。
1.xlswriteでできることできないこと
1-1 xlswriteの概要
xlswriteは古い関数であり、公式ページでは「非推奨」になっています。しかしながら「xlswriteを削除する予定はありません」ということも書いてあります。
xlswriteが非推奨の理由は、速度が遅いということがあるようですが、ここでは(速度については置いておいて)xlswriteで何ができるかについて述べます。
1-2 xlswriteによるEXCEL書き込み
まず、ファイル名が指定できます。これは当たり前ですが、xls, xlsx, xlsm, xlsbの拡張子を持つエクセルファイルを作成し、データを書き出すことができます。
次に、エクセルのセルに書き込む変数ですが、数値のみ、文字列のみ、混合の配列を書き込むことができます。混合配列を書き込む場合は、cell配列を使ってください。cell配列は、ざっくり言うと{ }で囲うことで作成することができます。
また、シート名をつけることができます。
そして、エクセルシートのどのセルに書くか(Range)を指定することができます。
ここまでをまとめてスクリプトに書くと次のようになります。
filename= 'test.xlsx';% ファイル名 拡張子がないと自動的に".xls"になる
A=magic(5);% 書き込むデータ
sheet='ABC';%書き込むシート
Range='B2';書き込む位置 省略した場合は左上がA1になる
xlswrite(filename,A,sheet,Range);%エクセルファイルに書き出し
シート名については、任意の名前をつけることができるのですが、'Sheet1'が強制的に作成されてしまいます。
シート名を数値の4とすると、'Sheet1'から'Sheet3'までが勝手に挿入されてしまいます。シート名が'Sheet4'ならば、'Sheet1'のみの追加で済みます。
シートを指定しない場合で、書き込む位置を指定したい場合は、書き込む配列の対角の左上と右下を指定する必要があります。左上だけを指定した場合は、'A1'から書き込まれます。
filename="test.xlsx";% ファイル名 拡張子がないと自動的に".xls"になる
A=magic(5);% 書き込むデータ
Range='B2:F7';%書き込む位置 対角をの2点を指定する
xlswrite(filename,A,Range);%エクセルファイルに書き出し
対角の値と配列の大きさが異なる場合にはエラーは出ず、このようになります。
同じファイル名で異なるシートに追記することができます。
例えば次の例では、'Sheet1'と'Sheet2'にそれぞれ書き込みができます。
filename="test.xlsx";% ファイル名 拡張子がないと自動的に".xls"になる
A=magic(5);% 書き込むデータ
sheet=2;%書き込むシート
Range='B2';%書き込む位置 省略した場合は左上がA1になる
xlswrite(filename,A,sheet,Range);%エクセルファイルに書き出し
B=magic(6);% 書き込むデータ
sheet=1;%書き込むシート
Range='C3';%書き込む位置 省略した場合は左上がA1になる
xlswrite(filename,B,sheet,Range);%エクセルファイルに書き出し
また、同じシートを指定して2回xlswriteを実行すると、上書きされます。
xlswriteでできることは、ここまでです。
2.writetable, writematrix,writecellで,できること&できないこと
2-1 新しい関数の概要
R2019aからはwritetable, writematrix,writecellという新しい書き込み関数が使えるようになりました。これにより実行速度の向上や、クロスプラットホームでの使用が可能になっています。
また、writetableは、表データを目的としたtable変数を書き込むのに特化しています。
これらの新しい関数において、xlswriteで不可能だったことについて説明します。
2-2 新しい関数の使い方
ここではwritematrixを用いて、エクセルファイルに書き込んでみます。
filename='test2.xlsx';% ファイル名 拡張子がないと自動的に"txt"になる
A=magic(5);% 書き込むデータ
sheet_n='ABC';%書き込むシート
range_n='B2';%書き込む位置 省略した場合は左上がA1になる
writematrix(A,filename,'Sheet',sheet_n,'Range',range_n);%エクセルファイルに書き出し
ファイル名の拡張子は、エクセル系の拡張子をつけます。つけないと.txtになります。
また、xlswriteでは、引数の順番が「ファイル名,データ」だったのが、新しい関数では「データ,ファイル名」と逆になります(だからmatrixwriteでなくてwritematrixなのかも)。
その他のオプションは、「オプション名,その値」となります。
公式ドキュメント見ると、オプション名の1文字目は大文字になっていますが、今、やったところだと小文字でも行けます。でも、大文字にしておいたほうが無難と思います。
(ちなみにロー&コラムの指定も'B4'でなく、'b4'でも行けます)。
「なんだか密度が濃くなってしまった」というのは列幅が自動で調整されたからです。
AutoFitWidth というオプションがあり、デフォルトでオンになっています。
自動調整しない場合は下記のように、ゼロを指定する、もしくはfalseを指定します。
writematrix(A,filename,'Sheet',sheet_n,'Range',range_n,'AutoFitWidth',0);%エクセルファイルに書き出し
見慣れた(?)感じになりました。
2-3 追記が可能になった
xlswriteだとデータは上書きされます。データを書き込んで、後で、追加のデータを同じシートに追記しようとしたとき(書き込む位置を指定します)、追記の位置を間違えると上書きして消してしまいかねません。
writematrixを使うと、同じシートのデータの後ろに書き足すことができます。
'WriteMode'というオプションに'append'を指定すればOKです(通常の上書きの場合は'WriteMode'は'inplace'で、これがデフォルトです)。
filename='test7.xlsx';% ファイル名
A=magic(5);% 書き込むデータ
sheet_n='ABC';%書き込むシート
range_n='B2';%書き込む位置 省略した場合は左上がA1になる
writematrix(A,filename,'Sheet',sheet_n,'Range',range_n);%エクセルファイルに書き出し
% ここから追記
B=A+2;% 書き込むデータその2
writematrix(B,filename,'Sheet',sheet_n,'WriteMode','append');%エクセルファイルに書き出し
ここで大事なのは、追記のときには'Range'オプションを指定しないことです。Rangeで指定された位置と、追記の位置のどちらに従えばよいかわからなくなるのでエラーが出ます。
2-4 余分なシートが追加されなくなる
新しい関数の場合には'Sheet1'が強制的に入ることはありません。必要なシートだけにできます。ここがxlswriteより進歩したところです。ただし、シートが1枚もないことは許されていません。
3.新しい関数でのできないことへの対処
シート操作は、上記のことはできるようになりましたが、エクセル単体で普通にできる「シート名の変更」はできません。また、「特定のシートだけを削除」することもできません。
敢えて言うならエクセル上の全てのデータをMATLABに移して、新しいエクセルファイルに転記することになりますが、そこまでの大技は使わないでしょう。
この辺については、MATLABの中の人もいろいろ考えていると思うので、充実していくことを期待したいと思います。
とは言うものの、「今すぐにやりたいことがある」という方もおられるかと思います。
MATLAB公式ページを見ますと、activexを用いて、MATLABではできないところを補う方法が書かれています。
ここでは、足りないところ(具体的にはエクセルシートに画像を貼りこむ)をpythonの助けを用いてやる方法を説明します。
3-1 pythonの準備
やることを最低限書きますと、
・pythonのインストール
・openpyxlのインストール
です。この辺はpython関連のページをググっていただければと思います。凝った環境は不要で、最低限動けばよいです。openpyxlはpythonでエクセルを操作するライブラリです。
※一点、補足です。この記事執筆時点のpythonの最新バージョンは3.12ですが、MATLABの最新バージョンR2023bを使う場合には、3.11.x以前でないと動かないようです。
3-2 matlabとpythonのスクリプト
MATLABとpythonのスクリプトがあり、fruitsフォルダの中には画像が入っています。
1)フォルダ名のfruitsというファイル名でエクセルファイルをつくる(fruits.xlsx)
2)シートを作成し、画像の名前をシート名とする
3)それぞれのシートに画像を貼りこむ
という内容を行ないます。
まず、MATLABのスクリプトです。
foldername='fruits';% フォルダ名指定
DIRn=append(foldername,'\');
files=dir([DIRn '*.png']);% 中の.pngファイルをリストアップ
fnum=length(files);% 画像ファイルの数
xlsname=[foldername '.xlsx'];% 作成するエクセルの名前
for t=1:fnum % 画像ファイルの数だけ回す
filefields=files(t);
fname=[DIRn filefields.name];
IMdisp=imread(fname);% 画像読み出し
TextA = {fname};% シートに書き込む文字
image_file=filefields.name;% 画像ファイルの名前
dum=split(image_file,'.');
fill_sheet=dum{1};% 書き込むシートの名前
% ファイルオープン&¥シート作成&書き込み
writecell(TextA,xlsname,'Sheet',fill_sheet,'Range','A3','AutoFitWidth',0);
% コマンド発行&python実行
comd=append("paint_image_excel.py ",xlsname," ",fill_sheet," ",image_file);
pyrunfile(comd);
end
最後の部分で、"pyrunfile"という関数を使って、pythonスクリプトを呼び出しています。
続いて呼び出される側のpythonのスクリプトです。
## paint_image_excel.py
import os
import argparse
import openpyxl
from openpyxl.drawing.image import Image
from openpyxl.utils import get_column_letter
def main(excel_file_name, sheet_name, image_file):
# エクセルファイルの有無を確認
if not os.path.isfile(excel_file_name):
print(f"Error: '{excel_file_name}' not found")
exit()
# エクセルファイルを読み込む
workbook = openpyxl.load_workbook(excel_file_name)
# シート名の指定
sheet = workbook[sheet_name]
# セルに文字を書き込む場合は、このようにする
directory = "fruits/"
# とりあえずrowを指定してみた
row = 12
sheet.cell(column=1, row=row).value = directory
img = Image(os.path.join(directory, image_file))
# 画像と位置を指定し、画像を貼りこむ(位置はB15)
sheet.add_image(img, 'B15')
# workbookの保存
workbook.save(excel_file_name)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("excel_file_name", help="使用する既存のExcelファイル名")
parser.add_argument("sheet_name", help="画像を貼りこむシート名")
parser.add_argument("image_name", help="画像名")
args = parser.parse_args()
main(args.excel_file_name, args.sheet_name, args.image_name)
3-3 スクリプトの実行
さて、MATLABのスクリプトを実行してみますと、エクセルのファイルが生成されます。
4.終わりに
以上、MATLABからEXCELに書き出す方法(その2)をまとめてみました。
間違いとか、「こんな使い方もある」がありましたら、お知らせいただけると幸いです。