MATLABからEXCELに書き出す

## はじめに
　昨年のアドベントカレンダーで、[『MATLABにファイルデータを書き出す関数』](https://qiita.com/Monzo_N/items/0927e71b2b38f524001f)を投稿したわけですが、早いもので、もう１年経ちました。
 本記事は、その続編で、EXCELに書き出すことに特化してまとめてあります。一部、重複があるかもしれません。

## １．xlswriteでできることできないこと
### 1-1 xlswriteの概要
　xlswriteは古い関数であり、公式ページでは「非推奨」になっています。しかしながら「xlswriteを削除する予定はありません」ということも書いてあります。
 xlswriteが非推奨の理由は、速度が遅いということがあるようですが、ここでは（速度については置いておいて）xlswriteで何ができるかについて述べます。
### 1-2 xlswriteによるEXCEL書き込み
 まず、ファイル名が指定できます。これは当たり前ですが、xls, xlsx, xlsm, xlsbの拡張子を持つエクセルファイルを作成し、データを書き出すことができます。
 次に、エクセルのセルに書き込む変数ですが、数値のみ、文字列のみ、混合の配列を書き込むことができます。混合配列を書き込む場合は、cell配列を使ってください。cell配列は、ざっくり言うと{ }で囲うことで作成することができます。
 また、シート名をつけることができます。
 そして、エクセルシートのどのセルに書くか(Range)を指定することができます。
 ここまでをまとめてスクリプトに書くと次のようになります。

 ```matlab
filename=　'test.xlsx';% ファイル名　拡張子がないと自動的に".xls"になる
A=magic(5);% 書き込むデータ
sheet='ABC';%書き込むシート
Range='B2';書き込む位置 省略した場合は左上がA1になる
xlswrite(filename,A,sheet,Range);%エクセルファイルに書き出し
```

シート名については、任意の名前をつけることができるのですが、'Sheet1'が強制的に作成されてしまいます。

![WS000415.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/18fc0c99-c1c1-3fc2-ae87-ab255cf156c8.jpeg)


シート名を数値の4とすると、'Sheet1'から'Sheet3'までが勝手に挿入されてしまいます。シート名が'Sheet4'ならば、'Sheet1'のみの追加で済みます。

シートを指定しない場合で、書き込む位置を指定したい場合は、書き込む配列の対角の左上と右下を指定する必要があります。左上だけを指定した場合は、'A1'から書き込まれます。

 ```matlab
filename="test.xlsx";% ファイル名　拡張子がないと自動的に".xls"になる
A=magic(5);% 書き込むデータ
Range='B2:F7';%書き込む位置 対角をの２点を指定する
xlswrite(filename,A,Range);%エクセルファイルに書き出し
```
対角の値と配列の大きさが異なる場合にはエラーは出ず、このようになります。

![WS000416.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/f79586dc-f5fb-b34c-ad25-3dc445c1c40c.jpeg)




同じファイル名で異なるシートに追記することができます。
例えば次の例では、'Sheet1'と'Sheet2'にそれぞれ書き込みができます。
 ```matlab
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);%エクセルファイルに書き出し
```
また、同じシートを指定して２回xlswriteを実行すると、上書きされます。

xlswriteでできることは、ここまでです。

## ２．writetable, writematrix,writecellで,できること&できないこと
### 2-1 新しい関数の概要
R2019aからはwritetable, writematrix,writecellという新しい書き込み関数が使えるようになりました。これにより実行速度の向上や、クロスプラットホームでの使用が可能になっています。
また、writetableは、表データを目的としたtable変数を書き込むのに特化しています。
これらの新しい関数において、xlswriteで不可能だったことについて説明します。
### 2-2 新しい関数の使い方
ここではwritematrixを用いて、エクセルファイルに書き込んでみます。

```matlab
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'でも行けます）。

![WS000411.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/653cfb54-16ab-b96c-e279-844521f698ac.jpeg)

「なんだか密度が濃くなってしまった」というのは列幅が自動で調整されたからです。
AutoFitWidth というオプションがあり、デフォルトでオンになっています。
自動調整しない場合は下記のように、ゼロを指定する、もしくはfalseを指定します。

```matlab
writematrix(A,filename,'Sheet',sheet_n,'Range',range_n,'AutoFitWidth',0);%エクセルファイルに書き出し 
```

![WS000410.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/68cc331c-e4b3-8ac9-c0d3-8a41d0b24155.jpeg)

見慣れた（？）感じになりました。

### 2-3 追記が可能になった
xlswriteだとデータは上書きされます。データを書き込んで、後で、追加のデータを同じシートに追記しようとしたとき（書き込む位置を指定します）、追記の位置を間違えると上書きして消してしまいかねません。
writematrixを使うと、同じシートのデータの後ろに書き足すことができます。
'WriteMode'というオプションに'append'を指定すればＯＫです（通常の上書きの場合は'WriteMode'は'inplace'で、これがデフォルトです）。

```matlab
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;% 書き込むデータその２
writematrix(B,filename,'Sheet',sheet_n,'WriteMode','append');%エクセルファイルに書き出し 
```
ここで大事なのは、追記のときには'Range'オプションを指定しないことです。Rangeで指定された位置と、追記の位置のどちらに従えばよいかわからなくなるのでエラーが出ます。

### 2-4 余分なシートが追加されなくなる

新しい関数の場合には'Sheet1'が強制的に入ることはありません。必要なシートだけにできます。ここがxlswriteより進歩したところです。ただし、シートが1枚もないことは許されていません。
![WS000410.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/e21be35f-80a9-3b49-d5ec-6d6b1d2a64a4.jpeg)

## ３．新しい関数でのできないことへの対処
シート操作は、上記のことはできるようになりましたが、エクセル単体で普通にできる「シート名の変更」はできません。また、「特定のシートだけを削除」することもできません。
敢えて言うならエクセル上の全てのデータを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のスクリプト
 今、作業ディレクトリの中は、このようになっているとします。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/5aea3a5f-c1f2-4100-c971-16f31231958d.png)

MATLABとpythonのスクリプトがあり、fruitsフォルダの中には画像が入っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/e359a064-96d2-0c6a-f680-149340c9970a.png)

１）フォルダ名のfruitsというファイル名でエクセルファイルをつくる（fruits.xlsx)
２）シートを作成し、画像の名前をシート名とする
３）それぞれのシートに画像を貼りこむ
という内容を行ないます。

まず、MATLABのスクリプトです。
```matlab:create_excel.m
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のスクリプトです。

```python:paint_image_excel.py
## 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のスクリプトを実行してみますと、エクセルのファイルが生成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/ae620652-32df-0b8e-b852-8bc87a7a35d8.png)

こんな感じにシートが生成できています。
![ExcelMOV.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/3f9ff50a-7e41-b64d-1b50-12e855dbdbb7.gif)



## ４．終わりに
以上、MATLABからEXCELに書き出す方法（その２）をまとめてみました。
間違いとか、「こんな使い方もある」がありましたら、お知らせいただけると幸いです。