MATLABとPythonは色々似ているところがあるので、どっちか使った経験がある人はもう一方を使おうとしたら勉強は難しくないはずです。
しかし同じように見えるのに実は違うところもある。こういうところはよく落とし穴になります。
もし間違ってエラーが出たらそれはまだしも、間違ってもエラーが出なくてそのまま間違う結果に導かれる場合もあって、それはわけのわからないバグの原因となりますね。
というわけで、今回の記事はそのようなよくある間違いの原因を羅列してみます。
一部はこの前の記事にも言及しましたので、重複の部分もありますが、今回は特にお互い違うところだけ注意します。
配列の仕様
1から0から
まず一番根本的に違い。それは配列のインデックスの数え方です。MATLABのインデックスは1から数えるのに対し、Pythonは0から。
ar = [5 6 7];
ar(1) % 5
ar = np.array([5,6,7])
ar[0] # 5
ar[1] # 6
基本すぎてあまり言うまでもないかもしれませんが、わかっていても意外とうっかりして間違えてしまうことです。
確かにMATLABみたいに1から数えた方が直感的でわかりやすいですね。でもそれは少数派です。C言語を始めとして主流の言語は殆ど0から始まるとされています。だから「0から始める」というのはプログラミングの常識とも言えます。これに慣れてしまったらMATLABを使うと逆に違和感を感じてしまいますね。
MATLABの他にFortran、R、Juliaも1からです。
この記事も参考。
順番に並ぶ要素の参照
MATLABもPythonもコロン:
を使って並んでいる要素を参照できますが、Pythonでは最後のインデックスの要素が含まないのに対し、MATLABは含まれます。だから同じような書いているのに、得られる要素の数は違ってしまいます。
ar = [10 11 12 13 14];
ar(2:4) % 11 12 13
ar = np.array([10,11,12,13,14])
ar[1:3] # [11, 12]
存在しなかった変数のインデックスへの代入
Pythonではいきなり長さより大きいインデックスを参照しようとしたらエラーになります。
ar = np.array([2,2,2])
ar[6] = 3 # IndexError: index 6 is out of bounds for axis 0 with size 3
MATLABの場合、普通に値を取ろうとしたらPythonと同じようにエラーになりますが、もしそれが代入だったらエラーにならずその場所に値が入って、その前の場所に0が入ってきます。
ar = [2 2 2];
ar(6) = 3 % ar が [2 2 2 0 0 3] になる
これはRubyも似ている仕様だから、そこまで意外ではないかもしれません。
ただしMATLABはそれどころか、元々存在しなかった変数もエラーが出なく、いきなり配列が作られます。
ara(4) = 5 % [0 0 0 5]になる
多次元でもできます。
arb(3,4) = 3
0 0 0 0
0 0 0 0
0 0 0 3
Pythonではいきなり宣言されていない変数にこんなことをしたら当然エラーになります。だからPythonを使っていた人がMATLABのこの仕様を見たらわけわからなくて悶々とするかもしれません。
代入でコピーか共有か
MATLABではある変数にある配列をもう一つの変数に代入したら配列はコピーされて、たとえ一方の中の要素が変更してももう一方に影響は全くありません。
ar1 = [1 2];
ar2 = ar1;
ar1(1) = 3;
ar1 % 3 2
ar2 % 1 2
代入したら値がコピーされるというのは当然だと思う人も多いと思いますが、実際にPythonも含め殆どの言語ではそうではないのです。代入で配列がコピーされず、データを共有することになってしまいます。そうしたら一方の中身が変わったらもう一方も変化します。
ar1 = np.array([1,2])
ar2 = ar1
ar1[0] = 3
ar1 # [3, 2]
ar2 # [3, 2]
Pythonのこの仕様は面倒だと思う人が少なくないでしょう。初心者がよくやらかしてしまうミスでもあります。
こんなことに気をつけなくてもいいのはMATLABの簡単でいいところの一つと言ってもいいでしょう。
因みにMATLABの他にPHPとSwiftも同じですが、これはただの少数派です。Pythonと同じように「参照型」は殆どです。
これについて以前私が記事を書いて、関数に渡す時などこの動作の違いに関連することはもっと詳しく書いてあります。
これらの記事も参考に。
行列の掛け算と割り算と冪乗
これはMATLABの一番厄介で注意しなければならないところだと思います。わかっていても気を抜くとついやらかしてしまって、非常にバグの原因になりやすいです。
Pythonで配列を掛け算したら普通に各要素の掛け算となりますね。
ar1 = np.array([[1,2],
[3,4]])
ar2 = np.array([[5,6],
[7,8]])
print(ar1*ar2)
[[ 5 12]
[21 32]]
これは「アダマール積」とも呼ばれます。普通の人にとってこれは直感的でわかりやすいですね。
しかしMATLABでは配列の掛け算が行列の「内積」と解釈されるので、同じ掛け算のように見えても結果は全然違うものになります。
ar1 = [1 2
3 4];
ar2 = [5 6
7 8];
ar1*ar2
19 22
43 50
そうならないように、普通にアダマール積をしたい場合は.*
と書きます。
ar1.*ar2
5 12
21 32
MATLABでは2次元配列を「行列」と呼んで、行列にとって掛け算というと普段は内積のことだから掛け算はこういう仕様になりますね。普段アダマール積の方は使われる場合が多いはずなのに。
掛け算だけでなく、割り算も同じようなことになります。割り算の場合は逆行列との内積になります。
ar1/ar2
3.0000 -2.0000
2.0000 -1.0000
普通に各要素の割り算にしたい場合は./
と書きます。
ar1./ar2
0.2000 0.3333
0.4286 0.5000
冪乗もそうです。MATLABでar^n
だと同じ行列をn回内積するということになります。
ar = np.array([[5,5],
[3,3]])
print(ar**2)
[[25 25]
[ 9 9]]
ar = [5 5
3 3];
ar^2
ar.^2
40 40
24 24
25 25
9 9
この記事と動画も参考に。
多次元から一次元で要素を並べる軸の順番
MATLABとPythonそれぞれ簡単に多次元配列の要素を全部一次元に並び替える方法がありますが、基本的な並べ方は違います。MATLABでは最初の軸から並べるが、Pythonは最後の軸から並べるのです。
2次元の場合1軸目は行で、2軸目は列となります。MATLABではまず1列目の全部の様相を並べてから次の行に行きます。
ar = [1 1 1
2 2 2];
ar(:)' % 1 2 1 2 1 2
Pythonでは逆に1行目の全部の要素から並べます。
ar = np.array([[1,1,1],
[2,2,2]])
print(ar.ravel()) # [1 1 1 2 2 2]
この順番を逆にしたい場合はまず'
や.T
などで転置行列しておいていいです。
ar = ar';
ar(:)' % 1 1 1 2 2 2
print(ar.T.ravel()) # [1 2 1 2 1 2]
多次元配列における参照の時の軸指定の省略
多次元の配列を参照する時にもしインデックスを一つだけ書いたらPythonでは1軸目を取るということになります。例えば2次元の場合はある行を取るということです。
ar = np.array([[3,6,4],
[4,4,4],
[5,5,7]])
print(ar[0]) # [3 6 4]
print(ar[1]) # [4 4 4]
print(ar[6]) # IndexError: index 6 is out of bounds for axis 0 with size 3
しかしMATLABでは1次元に並べ替えて一つだけ取るということになります。
ar = [3 6 4
4 4 4
5 5 7];
ar(1) % 3
ar(2) % 4
ar(6) % 5
もしPythonと同じようにある軸を取りたい場合他の軸に:
を書く必要があります。
ar(1,:) % 3 6 4
ar(2,:) % 4 4 4
Pythonでも同じように:
を書くことができますが、1軸目を取りたい場合は省略できます。MATLABで同じように省略してしまったら結果は全然違うので気をつける必要があります。
勝手に正方行列になっちゃう関数
MATLABもPythonもzeros
、ones
、randn
など色んな関数で指定したサイズの配列を作成することができて便利です。使い方は似ていますが、注意するべき違いがあります。
例えばPythonでサイズ3の1次元の配列を作成したいならこう書きます。
print(np.ones(3))
print(np.random.randn(3))
[1. 1. 1.]
[ 0.07970309 -1.28657592 -1.48870568]
しかしMATLABでこれらの関数にただ一つの数値を入れたらそれはそのサイズの正方行列を作ることになります。
disp(ones(3))
disp(randn(3))
1 1 1
1 1 1
1 1 1
-0.6701 0.5822 0.4509
0.3204 -0.2049 0.3786
-0.7197 0.4823 0.0309
もしつい間違えて1000000サイズを作るつもりで1000000×1000000を作ることになってしまったら、それは悲劇となるでしょう。意外とやらかしやすいことなので、気をつけないと。
一次元しか欲しくない場合はちゃんと1軸目に1を指定しなければなりません。
disp(ones(1,3))
disp(randn(1,3))
1 1 1
0.6654 0.2596 0.9332
配列をforループに使う時
MATLABもPythonもforを使ったらその中の要素でループを作ることができます。1次元配列に使う時は殆ど同じですが、2次元以上に使う場合は動作は少し違います。
例えば2次元の場合Pythonでは各行で繰り返すことになります。
ar = np.array([[5,6,7],
[7,8,9]])
for a in ar:
print(a)
[5 6 7]
[7 8 9]
それに対し、MATLABは各列で繰り返すことになります。
ar = [5 6 7
7 8 9];
for a = ar
disp(a)
end
5
7
6
8
7
9
このように軸の優先の違いはMATLABとPythonの動作の色んな擦れ違いの原因になりますね。
配列以外のこと
複数の返り値を受け取る時
関数は返り値を複数ある場合がありますね。普段返り値と同じ数の変数で受け取りますね。例えばこのような関数。
function [a,b,c] = fcn()
a = 1;
b = 2;
c = 3;
end
[x,y,z] = fc
x =
1
y =
2
z =
3
しかしもし受け取る変数が少ない場合、エラーが出ることはないけど、受け皿のない余分の返り値は捨てられます。
[x,y] = fc
x =
1
y =
2
また、受け取る変数がなくそのまま表示しようとする場合は1つ目の値しか出てこないので、2つ目以上表示して欲しい場合はちゃんと受け取る変数を書かないとなりません。
fc
ans =
1
Pythonでは受け取る返り値の数と受け皿の変数の数が違うとエラーになります。
def fcn():
return 1,2,3
x,y = fcn() # ValueError: too many values to unpack (expected 2)
ただし変数一つで受け取る場合その変数は全部の返り値が入っているtupleになります。
x = fcn()
print(x) # (1, 2, 3)
つまり一つ変数だけで受け取っておいても問題なく、後で分けてもいいです。
しかしMATLABでそんなことしたら本当に1つ目の返り値しか受け取れなくて、残りの返り値が捨てられてしまって後で欲しくてももう使えることができません。
" "
と' '
で作られた文字列の違い
Pythonでは" "
と' '
どちらを使っても同じように文字列を作ることができます。全く違いはありません。傾向として' '
が推奨されていますが、基本的に好きに使っても問題ないのです。
それに対し、MATLABでは2種類の文字列があります。" "
を使ったらデータ型はstring
で、他の言語の文字列に近い。その一方' '
を使ったらchar
の配列になり、本質はただの整数の塊になります。
MATLABでこの2種類の文字列はどっちを使ってもよくてあまり意識しなくてもいい場合が多いが、動作が違う場合もあるのでこれ意識する必要場合もあります。
例えば文字列を足し算することで連結することになりますね。Pythonも普通にこんなことができます。
a = 'あはは'
b = 'ばなな'
print(a+b) # あははばなな
MATLABでも" "
で囲んで作られた文字列なら同じようなことができます。
a = "あはは";
b = "ばなな";
disp(a+b) % あははばなな
しかし' '
で囲んで作られた文字列の場合動作は違って、このようなおかしな結果が出てきます。
a = 'あはは';
b = 'ばなな';
disp(a+b) % 24754 24793 24793
本質は数値だから足し算すると普通に数値の足し算になります。データ型も変更されます。
class(a) % 'char'
class(a+b) % 'double'
それどころか、引き算でも掛け算でもできてしまいます。" "
から作った文字列なら普通にエラーになります。
disp(a-b) % -46 5 5
disp(a.*b) % 153189600 153673206 153673206
違いはまだ色々ありますが、ここではただ違いがあることについて説明したいだけなのでこの辺にしておきます。
複素数の扱い
MATLABもPythonも複素数を簡単に扱えます。j
を後ろに書くだけで複素数になります。ただしPythonでは複素数が個別のデータ型であるのに対し、MATLABでは複素数でも実数でも同じdouble
でデータ型で区別しません。
type(1.) # float
type(1j) # complex
class(1.) % 'double'
class(1j) % 'double'
そのためか、MATLABでは負の数の平方根を求める時など適切な時に複素数になります。
sqrt(-3) % 0.0000 + 1.7321i
Pythonでは複素数型でない数の平方根を求めても複素数になりません。numpy関数を使う場合はnanとなります。ちゃんと予め複素数に変換する必要があります。
print(np.sqrt(-3)) # nan
print(np.sqrt(complex(-3))) # 1.7320508075688772j
また、これがnumpyではなく、mathモジュールの関数だったらエラーになります。
from math import sqrt
print(sqrt(-3)) # ValueError: math domain error
不適切なデータ型に対する%書式の処理
MATLABでもPythonでもC言語みたいな「%書式」が使えて便利です。MATLABはPythonやRubyみたいに文として簡単に書けるのではなく、PHPみたいにsprintfという関数として使うことになるのですが、基本的な使い方は同じです。
ただし、少し違うところがあります。例えばPythonでは%s
を使えばどのデータ型も表示できて便利です。
'x%3sx'%1 # 'x 1x'
'x%5sx'%1.1 # 'x 1.1x'
MATLABでこんなことしたら上手くいかず、変な結果になります。
sprintf("x%3sx",1) % "x x"
sprintf("x%5sx",1.1) % "x1.100000e+00x"
エラーではないけど、あるべき結果とは全然違う。だからMATLABで%s
は文字列を表示する時だけに使うことにしましょう。数値ならちゃんと%d
や%f
を使った方がいい。
また、%x
に浮動小数点数を入れる時などでPythonではエラーが出る場合もあります。
'%x'%1.1 # TypeError: %x format: an integer is required, not float
MATLABではどうしてもエラーが出ないが、勝手に適当な表示にされます。
sprintf("%x",1.1) % "1.100000e+00"
波括弧{ }
Pythonでは波括弧{ }
で囲んだらsetを作ることになります。
c = {1,2,3}
type(c) # set
MATLABでは{ }
で囲んだらcell配列となります。
c = {1 2 3};
class(c) % 'cell'
どれも本来の配列と似ているものだから初心者がぱっと見して同じものだと勘違いすることもあるかもしれませんが、全然別の概念です。
cell配列についてこの記事に纏めておいたのでこの記事をおすすめします。
終わりに
以上思いついたよくある擦れ違いについて説明しました。その他にもまだあると思いますが、後で思いついたら追加するかもしれません。
まだ何か重要なことを見落としていたら補足していただけたら嬉しいです。