統計 - 静かなる名辞 https://www.haya-programming.com/archive/category/%E7%B5%B1%E8%A8%88 pythonとプログラミングのこと Thu, 07 May 2020 20:42:34 +0900 http://blogs.law.harvard.edu/tech/rss Hatena::Blog 【python】matplotlibのboxplotで外れ値を表示しないようにする https://www.haya-programming.com/entry/2019/10/07/202416 <div class="section"> <h3>はじめに</h3> <p> matplotlibのboxplotを使うと簡単に箱ひげ図が描けます。ただし、デフォルト設定では外れ値が黒い円で表示されます。</p><p> どんなデータでも、サンプル数が多いと一定数の外れ値は出てしまいます。ただ、図を見る人は気にするところですし、外れ値がたくさんあると見た目にも悪いので、何らかの処置が必要です。</p><p> <b>外れ値が描画されてしまうプログラムの例</b></p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) x = np.random.normal(size=<span class="synConstant">3</span>*<span class="synConstant">10</span>**<span class="synConstant">3</span>) <span class="synComment"># サンプル数に注目</span> plt.boxplot(x) plt.savefig(<span class="synConstant">&quot;result1.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="外れ値がたくさん出ている"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191007/20191007201240.png" alt="&#x5916;&#x308C;&#x5024;&#x304C;&#x305F;&#x304F;&#x3055;&#x3093;&#x51FA;&#x3066;&#x3044;&#x308B;" title="f:id:hayataka2049:20191007201240p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result1.png 外れ値がたくさん出ている</figcaption></figure></p><p> ということで、外れ値を表示させない方法を解説します。なお、この記事の内容は公式リファレンスに基づいています。</p><p><a href="https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.boxplot.html">matplotlib.pyplot.boxplot &mdash; Matplotlib 3.1.1 documentation</a></p><p></p> </div> <div class="section"> <h3>シンプルに表示させない</h3> <p> boxplotのキーワード引数のsymを使うと外れ値を「描画させない」設定が可能になります。具体的には、空文字列を指定します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) x = np.random.normal(size=<span class="synConstant">3</span>*<span class="synConstant">10</span>**<span class="synConstant">3</span>) plt.boxplot(x, sym=<span class="synConstant">&quot;&quot;</span>) <span class="synComment"># 変更点</span> plt.savefig(<span class="synConstant">&quot;result2a.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="外れ値が表示されなくなった"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191007/20191007201415.png" alt="&#x5916;&#x308C;&#x5024;&#x304C;&#x8868;&#x793A;&#x3055;&#x308C;&#x306A;&#x304F;&#x306A;&#x3063;&#x305F;" title="f:id:hayataka2049:20191007201415p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result2a.png 外れ値が表示されなくなった</figcaption></figure></p><p> 注意点としては、これはあくまでも外れ値の描画を行わないだけで、外れ値そのものはデフォルト通り計算されるということが挙げられます。つまり、本来の最大値・最小値よりひげが短くなります。</p><p> プロット上でだけ「消している」ということですね。</p><p> 参考:<br /> <a href="https://bellcurve.jp/statistics/course/5222.html">4-3. &#x5916;&#x308C;&#x5024;&#x691C;&#x51FA;&#x306E;&#x3042;&#x308B;&#x7BB1;&#x3072;&#x3052;&#x56F3; | &#x7D71;&#x8A08;&#x5B66;&#x306E;&#x6642;&#x9593; | &#x7D71;&#x8A08;WEB</a></p><p> また、外れ値のマーカーに好きな記号を指定したりすることもできます。本来はこの用途で用いる引数です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) x = np.random.normal(size=<span class="synConstant">3</span>*<span class="synConstant">10</span>**<span class="synConstant">3</span>) plt.boxplot(x, sym=<span class="synConstant">&quot;+&quot;</span>) <span class="synComment"># 変更点</span> plt.savefig(<span class="synConstant">&quot;result2b.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="マーカーを十字にしてみた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191007/20191007201833.png" alt="&#x30DE;&#x30FC;&#x30AB;&#x30FC;&#x3092;&#x5341;&#x5B57;&#x306B;&#x3057;&#x3066;&#x307F;&#x305F;" title="f:id:hayataka2049:20191007201833p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result2b.png マーカーを十字にしてみた</figcaption></figure></p><p> 使えるマーカーの一覧はこちらを参照。<br /> <a href="https://matplotlib.org/3.1.1/api/markers_api.html">matplotlib.markers &mdash; Matplotlib 3.1.1 documentation</a><br /> </p> </div> <div class="section"> <h3>外れ値の計算そのものをやめる</h3> <p> (外れ値も含めた)本来の最大値・最小値に基づいてひげを出す場合は、whis="range"を指定します。</p><p> そもそも外れ値の概念なしで箱ひげ図を描くというやり方になります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt np.random.seed(<span class="synConstant">0</span>) x = np.random.normal(size=<span class="synConstant">3</span>*<span class="synConstant">10</span>**<span class="synConstant">3</span>) plt.boxplot(x, whis=<span class="synConstant">&quot;range&quot;</span>) <span class="synComment"># 変更点</span> plt.savefig(<span class="synConstant">&quot;result3.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="result3.png 上の方法で非表示にするよりひげが長くなる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20191007/20191007202110.png" alt="result3.png&#x3000;&#x4E0A;&#x306E;&#x65B9;&#x6CD5;&#x3067;&#x975E;&#x8868;&#x793A;&#x306B;&#x3059;&#x308B;&#x3088;&#x308A;&#x3072;&#x3052;&#x304C;&#x9577;&#x304F;&#x306A;&#x308B;" title="f:id:hayataka2049:20191007202110p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result3.png 上の方法で非表示にするよりひげが長くなる</figcaption></figure></p><p> この方が誤解を招く恐れがないので(つまり、外れ値検出をやっておいて外れ値をプロットしないという図は少しイレギュラーで伝わりづらい気がするので)、これでやるのがおすすめです。ただし、外れ値が少なくて、そんな極端に外れていないとき向きのやり方です(やたらひげが長いのも不格好ですから……)。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 二つやり方がありますが、意味が微妙に違うし、実際にそれぞれで異なった結果になるので注意してください。</p> </div> Mon, 07 Oct 2019 20:24:16 +0900 hatenablog://entry/26006613446026969 python matplotlib Tips 可視化 統計 【python】相関係数行列をstatsmodelsを使って描く https://www.haya-programming.com/entry/2019/07/21/015442 <div class="section"> <h3>はじめに</h3> <p> 相関係数行列を描く方法としては、pandasとseabornを使う方法などが一般的です。しかし、statsmodelsで行う方法も実は存在します。</p><p><b>pandas+seabornでやる場合</b></p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">import</span> seaborn <span class="synStatement">as</span> sns np.random.seed(<span class="synConstant">0</span>) df = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>:np.random.randint(<span class="synConstant">0</span>, <span class="synConstant">10</span>, size=(<span class="synConstant">10</span>,)), <span class="synConstant">&quot;B&quot;</span>:np.random.randint(<span class="synConstant">0</span>, <span class="synConstant">10</span>, size=(<span class="synConstant">10</span>,)), <span class="synConstant">&quot;C&quot;</span>:np.random.randint(<span class="synConstant">0</span>, <span class="synConstant">10</span>, size=(<span class="synConstant">10</span>,))}) df_corr = df.corr() sns.heatmap(df_corr, vmax=<span class="synConstant">1</span>, vmin=-<span class="synConstant">1</span>, center=<span class="synConstant">0</span>) plt.savefig(<span class="synConstant">&quot;pandas_cm.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="pandas_cm.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190721/20190721014633.png" alt="pandas_cm.png" title="f:id:hayataka2049:20190721014633p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>pandas_cm.png</figcaption></figure></p><p> 参考:<a href="https://note.nkmk.me/python-pandas-corr/">pandas.DataFrame&#x306E;&#x5404;&#x5217;&#x9593;&#x306E;&#x76F8;&#x95A2;&#x4FC2;&#x6570;&#x3092;&#x7B97;&#x51FA;&#x3001;&#x30D2;&#x30FC;&#x30C8;&#x30DE;&#x30C3;&#x30D7;&#x3067;&#x53EF;&#x8996;&#x5316; | note.nkmk.me</a></p><p> というか<a href="https://www.haya-programming.com/entry/2019/07/18/041646">&#x76F8;&#x95A2;&#x4FC2;&#x6570;&#x306E;&#x8A18;&#x4E8B;</a>を書くときにstatsmodelsのリファレンスを検索していたらこれを見つけたので、試してみようと思った次第です。どれくらい使い物になるのでしょうか。</p> </div> <div class="section"> <h3>使い方</h3> <p> ずばり、これです。</p><p><a href="https://www.statsmodels.org/stable/generated/statsmodels.graphics.correlation.plot_corr.html">statsmodels.graphics.correlation.plot_corr &mdash; statsmodels v0.10.1 documentation</a></p><p> 名前が覚えづらいのが最大の難点で、他は普通に使えます。というか、seaborn.heatmapとそんなに変わりません。</p><p> 公式で紹介されている使い方。</p> <blockquote> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt &gt;&gt;&gt; <span class="synPreProc">import</span> statsmodels.graphics.api <span class="synStatement">as</span> smg &gt;&gt;&gt; hie_data = sm.datasets.randhie.load_pandas() &gt;&gt;&gt; corr_matrix = np.corrcoef(hie_data.data.T) &gt;&gt;&gt; smg.plot_corr(corr_matrix, xnames=hie_data.names) &gt;&gt;&gt; plt.show() </pre> </blockquote> <p> 相関行列の計算からやってくれるわけではなく、例ではnumpyで計算しているようです。</p><p> これを踏まえて書いたコード。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> statsmodels.graphics.correlation <span class="synPreProc">import</span> plot_corr np.random.seed(<span class="synConstant">0</span>) df = pd.DataFrame({<span class="synConstant">&quot;A&quot;</span>:np.random.randint(<span class="synConstant">0</span>, <span class="synConstant">10</span>, size=(<span class="synConstant">10</span>,)), <span class="synConstant">&quot;B&quot;</span>:np.random.randint(<span class="synConstant">0</span>, <span class="synConstant">10</span>, size=(<span class="synConstant">10</span>,)), <span class="synConstant">&quot;C&quot;</span>:np.random.randint(<span class="synConstant">0</span>, <span class="synConstant">10</span>, size=(<span class="synConstant">10</span>,))}) df_corr = df.corr() plot_corr(df_corr, xnames=<span class="synConstant">&quot;ABC&quot;</span>) plt.savefig(<span class="synConstant">&quot;statsmodels_cm.png&quot;</span>) </pre><p> 満足の行く図にするためには、最低限xnamesを指定する必要があります。seaborn.heatmapでいうxticklabels, yticklabelsですね。iterableを渡せば良いようです。</p><p> 結果。</p><p><figure class="figure-image figure-image-fotolife" title="statsmodels_cm.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190721/20190721014658.png" alt="statsmodels_cm.png" title="f:id:hayataka2049:20190721014658p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>statsmodels_cm.png</figcaption></figure></p><p> 見た目が少し違いますね。デフォルトのカラーマップはこっちの方が良いかも。</p><p> それだけ? と思ってリファレンスを見直したら、面白そうな引数が2つだけありました。</p> <blockquote> <p>title<br /> str, optional<br /> The figure title. If None, the default (‘Correlation Matrix’) is used. If title='', then no title is added.</p> </blockquote> <blockquote> <p>normcolor<br /> bool or tuple of scalars, optional<br /> If False (default), then the color coding range corresponds to the range of dcorr. If True, then the color range is normalized to (-1, 1). If this is a tuple of two numbers, then they define the range for the color bar.</p> </blockquote> <p> normcolorはカラーマップの範囲を-1~1に一致させてくれるので、その方が良いと思います。同じことをseabornでできないか調べましたが、ぱっと見なさそうなので、アドバンテージです。</p><p> 両方設定してみましょう。<br />  </p> <pre class="code lang-python" data-lang="python" data-unlink>plot_corr(df_corr, xnames=<span class="synConstant">&quot;ABC&quot;</span>, title=<span class="synConstant">&quot;random&quot;</span>, normcolor=<span class="synIdentifier">True</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="statsmodels_cm.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190721/20190721015247.png" alt="statsmodels_cm.png" title="f:id:hayataka2049:20190721015247p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>statsmodels_cm.png</figcaption></figure></p><p> それなりに満足感のある結果。</p> </div> <div class="section"> <h3>まとめ</h3> <p> どうということはありませんが、欠点もなさそうだし、使いやすいのでこちらでやってもいいはず。</p><p> 難点は、やはり名前が覚えづらいこと(importを書くときに迷う)くらいでしょうか。</p> </div> Sun, 21 Jul 2019 01:54:42 +0900 hatenablog://entry/17680117127219947978 python statsmodels 可視化 統計 Tips pythonで相関係数を計算する方法いろいろ3種類 https://www.haya-programming.com/entry/2019/07/18/041646 <div class="section"> <h3 id="はじめに">はじめに</h3> <p> pythonで相関係数を計算する方法はいろいろあります。確認したら、主要ライブラリだけで3つありました。</p><p> いろいろあるということは用途によって使い分けられるということなので、淡々と書いていきます。</p><p> なお、念のために断っておくと、ここで書いている「相関係数」はすべて「ピアソンの積立相関係数」です。順位相関などはまた別に調べてください(ただしpandasを使う方法だと出せます)。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#データの確認">データの確認</a></li> <li><a href="#numpyでやる">numpyでやる</a></li> <li><a href="#pandasでやる">pandasでやる</a></li> <li><a href="#scipyを使う">scipyを使う</a></li> <li><a href="#あと思ったこととか">あと思ったこととか</a></li> <li><a href="#まとめ">まとめ</a></li> </ul> </div> <div class="section"> <h3 id="データの確認">データの確認</h3> <p> 予め以下のようなデータを定義しておきます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; np.random.seed(<span class="synConstant">0</span>) &gt;&gt;&gt; x = np.arange(<span class="synConstant">0</span>, <span class="synConstant">10</span>, <span class="synConstant">0.1</span>) &gt;&gt;&gt; y = x + np.random.normal(size=x.shape) </pre><p> 散布図にプロットして確認。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt &gt;&gt;&gt; plt.scatter(x, y) &lt;matplotlib.collections.PathCollection <span class="synIdentifier">object</span> at <span class="synConstant">0x7f31aa415f28</span>&gt; &gt;&gt;&gt; plt.savefig(<span class="synConstant">&quot;fig.png&quot;</span>) </pre><p><figure class="figure-image figure-image-fotolife" title="fig.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190718/20190718035459.png" alt="fig.png" title="f:id:hayataka2049:20190718035459p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fig.png</figcaption></figure></p><p> もう少しサンプル数が少なくても良かったような気もしますが、せっかく定義したのでこれでやります。</p> </div> <div class="section"> <h3 id="numpyでやる">numpyでやる</h3> <p> numpyの場合はnp.corrcoefで相関係数「行列」を出してくれます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; np.corrcoef(x, y) array([[<span class="synConstant">1.</span> , <span class="synConstant">0.94129622</span>], [<span class="synConstant">0.94129622</span>, <span class="synConstant">1.</span> ]]) </pre><p> 0.9以上なので強い相関があるみたいです。「行列」が出てくるので、単に相関係数がほしいときは適当に取り出します。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; np.corrcoef(x, y)[<span class="synConstant">0</span>, <span class="synConstant">1</span>] <span class="synConstant">0.9412962237004372</span> </pre><p> あまりスマートではないので、本当に相関係数「行列」がほしいときに使います。</p><p><a href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.corrcoef.html">numpy.corrcoef &mdash; NumPy v1.17 Manual</a></p><p></p> </div> <div class="section"> <h3 id="pandasでやる">pandasでやる</h3> <p> pandasでもnumpyと同じことができるようです。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd &gt;&gt;&gt; df = pd.DataFrame({<span class="synConstant">&quot;x&quot;</span>:x, <span class="synConstant">&quot;y&quot;</span>:y}) &gt;&gt;&gt; df.corr() x y x <span class="synConstant">1.000000</span> <span class="synConstant">0.941296</span> y <span class="synConstant">0.941296</span> <span class="synConstant">1.000000</span> </pre><p> 行と列に名前がついて使いやすくなったと思います。また、ピアソン以外の相関係数も、kendall, spearmanをmethod引数に渡すことができ、なんならcallableで任意の関数で計算することもできるといった使いやすさがあります。多機能ですね。</p><p><a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.corr.html">pandas.DataFrame.corr &mdash; pandas 0.25.1 documentation</a></p><br /> <p> あと、相関係数「行列」がほしいときはpandasを経由した方が便利でしょうか。seabornに投げて可視化するときに、行・列の名前を考慮してくれるので、便利そうです。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.nkmk.me%2Fpython-pandas-corr%2F" title="pandas.DataFrameの各列間の相関係数を算出、ヒートマップで可視化 | note.nkmk.me" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://note.nkmk.me/python-pandas-corr/">pandas.DataFrame&#x306E;&#x5404;&#x5217;&#x9593;&#x306E;&#x76F8;&#x95A2;&#x4FC2;&#x6570;&#x3092;&#x7B97;&#x51FA;&#x3001;&#x30D2;&#x30FC;&#x30C8;&#x30DE;&#x30C3;&#x30D7;&#x3067;&#x53EF;&#x8996;&#x5316; | note.nkmk.me</a></p><p></p> </div> <div class="section"> <h3 id="scipyを使う">scipyを使う</h3> <p> 漢は黙ってscipy、という価値観が私にはあります。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> scipy <span class="synPreProc">import</span> stats &gt;&gt;&gt; stats.pearsonr(x, y) (<span class="synConstant">0.941296223700437</span>, <span class="synConstant">5.153124094421605e-48</span>) </pre><p> 勝手に両側検定をやってp値を出してくれています(結果のtupleの0から数えて1つめ)。</p><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html">scipy.stats.pearsonr &mdash; SciPy v1.3.0 Reference Guide</a></p><p> 検定やってくれるのはいいですね。普通は別途やる必要があると思います。</p> </div> <div class="section"> <h3 id="あと思ったこととか">あと思ったこととか</h3> <ul> <li>なんで標準のstatisticsで用意されてないの</li> <li>statsmodelsは高度な機能はいろいろ提供しているくせに、ただの相関係数の出し方がいくらググっても出てこないのはなんで。リファレンスすごく読みづらいし。あるかもしれないけど諦めた</li> </ul> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> まあ、3つあればいいか……行列がほしいときは楽そうなのはpandas、単に数字がほしければscipyという使い分けになりそうですね。</p> </div> Thu, 18 Jul 2019 04:16:46 +0900 hatenablog://entry/17680117127218871488 python Tips numpy scipy pandas 統計 scikit-learnで目的変数を対数変換したりするTransformedTargetRegressor https://www.haya-programming.com/entry/2019/07/14/043324 <div class="section"> <h3>はじめに</h3> <p> 経済系の分析などで、目的変数を対数変換して分析するというケースがあります。scikit-learnはそのようなケースもサポートしています。</p><p> どうやったらいいのかわからなくて、自分で変数を変換している人も中にはいるかと思いますが、モデル構築まではなんとかなっても、予測のことまで考えると不便になります。うまくPipelineなどで自動化できるといいのですが、普通のやり方では目的変数は処理してくれません。しかし、TransformedTargetRegressorなら大丈夫です。<br /> <br /> </p> </div> <div class="section"> <h3>目的変数の対数変換</h3> <p> sklearn.compose.TransformedTargetRegressorを使います。ほとんど紹介を見かけないのですが、実際これでできます。</p> <blockquote> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LinearRegression &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.compose <span class="synPreProc">import</span> TransformedTargetRegressor &gt;&gt;&gt; tt = TransformedTargetRegressor(regressor=LinearRegression(), ... func=np.log, inverse_func=np.exp) &gt;&gt;&gt; X = np.arange(<span class="synConstant">4</span>).reshape(-<span class="synConstant">1</span>, <span class="synConstant">1</span>) &gt;&gt;&gt; y = np.exp(<span class="synConstant">2</span> * X).ravel() &gt;&gt;&gt; tt.fit(X, y) TransformedTargetRegressor(...) &gt;&gt;&gt; tt.score(X, y) <span class="synConstant">1.0</span> &gt;&gt;&gt; tt.regressor_.coef_ array([<span class="synConstant">2.</span>]) </pre><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.compose.TransformedTargetRegressor.html">sklearn.compose.TransformedTargetRegressor &mdash; scikit-learn 0.21.2 documentation</a><br /> </p> </blockquote> <p> なかなか特殊な感じですね。モデルを作るときにfuncとinverse_funcを渡すのがミソで、学習はfuncで変換された目的変数に対して行われます。予測のときはinverse_funcで逆変換されて出てくるので、余計な手間をすべて省くことができます。</p> </div> <div class="section"> <h3>やってみる</h3> <p> ということで、対数変換した方がうまくいくようなデータを作って線形回帰してみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LinearRegression <span class="synPreProc">from</span> sklearn.compose <span class="synPreProc">import</span> TransformedTargetRegressor <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): np.random.seed(<span class="synConstant">0</span>) x = np.linspace(<span class="synConstant">0</span>, <span class="synConstant">5</span>, <span class="synConstant">100</span>) y = <span class="synConstant">20</span> + np.exp(x + np.random.normal(scale=<span class="synConstant">0.5</span>, size=x.shape)) X = x.reshape(-<span class="synConstant">1</span>, <span class="synConstant">1</span>) lr = LinearRegression() lr_logy = TransformedTargetRegressor( regressor=lr, func=np.log, inverse_func=np.exp) plt.scatter(x, y, c=<span class="synConstant">&quot;b&quot;</span>, alpha=<span class="synConstant">0.1</span>) <span class="synStatement">for</span> name, model <span class="synStatement">in</span> [(<span class="synConstant">&quot;Linear&quot;</span>, lr), (<span class="synConstant">&quot;Linear-logy&quot;</span>, lr_logy)]: model.fit(X, y) pred = model.predict(X) plt.plot(x, pred, label=name) plt.legend() plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190714/20190714020620.png" alt="result.png" title="f:id:hayataka2049:20190714020620p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> 非等分散だったりして条件が悪いのですが、線形回帰で素直にやるのと比べると良いフィッティングを見せています。</p> </div> <div class="section"> <h3>おまけ:説明変数を対数変換したいとき</h3> <p> FunctionTransformerを使ってください。</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.FunctionTransformer.html">sklearn.preprocessing.FunctionTransformer &mdash; scikit-learn 0.21.2 documentation</a></p><p></p> <blockquote> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> FunctionTransformer &gt;&gt;&gt; transformer = FunctionTransformer(np.log1p, validate=<span class="synIdentifier">True</span>) &gt;&gt;&gt; X = np.array([[<span class="synConstant">0</span>, <span class="synConstant">1</span>], [<span class="synConstant">2</span>, <span class="synConstant">3</span>]]) &gt;&gt;&gt; transformer.transform(X) array([[<span class="synConstant">0.</span> , <span class="synConstant">0.69314718</span>], [<span class="synConstant">1.09861229</span>, <span class="synConstant">1.38629436</span>]]) </pre><p><a href="https://scikit-learn.org/stable/modules/preprocessing.html#function-transformer">5.3. Preprocessing data &mdash; scikit-learn 0.21.2 documentation</a></p> </blockquote> <p> Pipelineで組み合わせれば、説明変数・目的変数ともに対数変換でloglogというのも簡単です。</p> </div> <div class="section"> <h3>まとめ</h3> <p> 対数変換した方がうまくいくようなデータのときは試してみましょう。もちろん対数以外の変換でもいけるので、目的変数を変換したいときはこれでいいと思います。</p> </div> Sun, 14 Jul 2019 04:33:24 +0900 hatenablog://entry/17680117127217382245 python 機械学習 統計 sklearn Tips 回帰 ロジスティック回帰が線形分離不可能な分類問題を解けないことの説明 https://www.haya-programming.com/entry/2019/07/07/033621 <div class="section"> <h3>はじめに</h3> <p> ロジスティック回帰が線形分離不可能な分類問題を解けないことは有名な話です。だけど、「いや解けるだろ」「なんで解けないの???」と言われてしまうことがあるので<a href="#f-004c244d" name="fn-004c244d" title="……">*1</a>、それができないことを説明しておこうと思います。</p><p> なお、この記事はこちらの記事を参考にしています。</p><p><iframe src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fill-identified.hatenablog.com%2Fentry%2F2018%2F05%2F23%2F233955" title="誤った図解から学ぶロジスティック回帰の性質 - ill-identified diary" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="http://ill-identified.hatenablog.com/entry/2018/05/23/233955">&#x8AA4;&#x3063;&#x305F;&#x56F3;&#x89E3;&#x304B;&#x3089;&#x5B66;&#x3076;&#x30ED;&#x30B8;&#x30B9;&#x30C6;&#x30A3;&#x30C3;&#x30AF;&#x56DE;&#x5E30;&#x306E;&#x6027;&#x8CEA; - ill-identified diary</a></p><p> 書きたいことは言い尽くされている感もあるので、こういう結論に至る過程を数式で書きます<a href="#f-a127b405" name="fn-a127b405" title="誰でも納得するから">*2</a>。</p> </div> <div class="section"> <h3>y=0.5を代入すればいい</h3> <p> さて、説明変数<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20x_i" alt=" x_i"/>、目的変数<img src="https://chart.apis.google.com/chart?cht=tx&chl=y" alt="y"/>、パラメータ<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cbeta_i" alt=" \beta_i"/>、などを適当に定めたとします。すると、ロジスティック回帰の予測式はこんなやつになります(<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cbeta_i" alt=" \beta_i"/>は適当に学習できたとする)。</p><p>\begin{align}<br /> \hat{y} = \frac{1}{1 + <br /> \mathrm{e}^{-(\beta_0 + \sum_{i=1}^{n}\beta_i x_i)}<br /> }<br /> \end{align}</p><p> 書き方の流儀はいろいろあると思いますが(<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Calpha" alt=" \alpha"/>を使うとか)、今回は上の式で行きます。</p><p> さて、今回は分離超平面の式に興味があるのでしたね。分離超平面ってどこ? というと、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20y%3D0.5" alt=" y=0.5"/>のところです。なので、淡々と式を書き換えます。</p><p>\begin{align}<br /> 0.5 = \frac{1}{1 + <br /> \mathrm{e}^{-(\beta_0 + \sum_{i=1}^{n}\beta_i x_i)}<br /> }<br /> \end{align}<br />  <br />  両辺を逆数にします。</p><p>\begin{align}<br /> 2 = 1 + \mathrm{e}^{-(\beta_0 + \sum_{i=1}^{n}\beta_i x_i)}<br /> \end{align}</p><p> とりあえず邪魔な1を反対側に移す。</p><p>\begin{align}<br /> 1 = \mathrm{e}^{-(\beta_0 + \sum_{i=1}^{n}\beta_i x_i)}<br /> \end{align}</p><p> 両辺の対数を取る。</p><p>\begin{align}<br /> 0 = -(\beta_0 + \sum_{i=1}^{n}\beta_i x_i)<br /> \end{align}</p><p> マイナスは必要ないので消しましょう。</p><p>\begin{align}<br /> 0 = \beta_0 + \sum_{i=1}^{n}\beta_i x_i<br /> \end{align}</p><p> もうだいたい終わってる気もしますが、たとえば<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20n%3D2" alt=" n=2"/>として適当に式を変形します。</p><p>\begin{align}<br /> \beta_0 + \beta_1 x_1 + \beta_2 x_2 = 0<br /> \end{align}</p><p> ……はい、これは「直線の式」ですね。</p><p> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20n" alt=" n"/>が増えると係数と変数が増えていきますが、いずれにせよ線形の式なのは間違いありません。<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20x_i%5E2" alt=" x_i^2"/>とか<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathrm%7Be%7D%5E%7Bx_i%7D" alt=" \mathrm{e}^{x_i}"/>みたいなのが出てくる余地はありません。</p><p> 「分離超平面」がかのような式で表わせる以上、線形分離不可能な分類問題は解けない、ということです。</p> </div> <div class="section"> <h3>非線形の問題も解く方法</h3> <p> あくまでも「分離境界が線形にならないといけない」というだけなので、データを非線形変換して解けるような空間に写像すればできます。代表的な方法は多項式を使うことです(SVMの多項式カーネルなんかと同じですが、明示的に特徴量空間を計算するのが相違点です)。</p><p> ということで、こちらの記事を御覧ください。どれくらい非線形でも行けるのかが書いてあります。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F07%2F08%2F042121" title="非線形がなんだ! ロジスティック回帰+多項式でやってやる! - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/07/08/042121">&#x975E;&#x7DDA;&#x5F62;&#x304C;&#x306A;&#x3093;&#x3060;&#xFF01; &#x30ED;&#x30B8;&#x30B9;&#x30C6;&#x30A3;&#x30C3;&#x30AF;&#x56DE;&#x5E30;&#xFF0B;&#x591A;&#x9805;&#x5F0F;&#x3067;&#x3084;&#x3063;&#x3066;&#x3084;&#x308B;&#xFF01; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p> </div><div class="footnote"> <p class="footnote"><a href="#fn-004c244d" name="f-004c244d" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">……</span></p> <p class="footnote"><a href="#fn-a127b405" name="f-a127b405" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">誰でも納得するから</span></p> </div> Sun, 07 Jul 2019 03:36:21 +0900 hatenablog://entry/17680117127214398918 統計 雑記 ロジスティック回帰 コサイン距離は距離じゃないんだから、勘違いしないでよねっ! https://www.haya-programming.com/entry/2019/07/05/030935 <div class="section"> <h3>き、記事タイトルに意味なんてないんだからねっ!</h3> <p> 自然言語処理などでお馴染みのコサイン類似度。これを1から引いたものを「コサイン距離」と称している文献も散見されますが、この「コサイン距離」は距離としての性質を満たしません。</p><p> それがどういうことなのかをこの記事で説明していきます。</p> </div> <div class="section"> <h3>コサイン類似度のことくらい自分で調べなさいっ!</h3> <p> まず前提となるコサイン類似度については、親切に解説しているサイトが他にたくさんあるので、そちらに譲ります。</p><p> たとえばここなどがいいでしょう。</p><p><a href="http://www.cse.kyoto-su.ac.jp/~g0846020/keywords/cosinSimilarity.html">&#x30B3;&#x30B5;&#x30A4;&#x30F3;&#x985E;&#x4F3C;&#x5EA6;</a></p><p> コサイン類似度はベクトル同士の類似度であり、要するに単なる内積(をノルムで正規化したもの)です。これは-1から1の区間を取ります。1なら「最も似ている(同じベクトル)」、-1なら「最も似ていない(反対向き)」という性質を持ちます。</p><p> これを1から引くことで、0なら「最も似ている」、2なら「最も似ていない」に変換したものが「コサイン距離」です。</p> </div> <div class="section"> <h3>距離の定義を知らないの? しょ、しょうがないから教えてあげるわ</h3> <p> さて、距離という言葉というか概念は実は数学的にちゃんと定義できます。かいつまんで書くと、関数<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20d" alt=" d"/>が以下の条件(距離の公理といいます)を満たすとき、その関数を距離関数あるいは距離と言えます。</p><p>\begin{align}<br /> d(x,y) &>& 0\\<br /> x&=&y\Leftrightarrow d(x, y) = 0\\<br /> d(x, y) &=& d(y, x)\\<br /> d(x, z) &\leq& d(x, y) + d(y, z)<br /> \end{align}</p><p>参考<br /> <a href="https://ja.wikipedia.org/wiki/%E8%B7%9D%E9%9B%A2%E7%A9%BA%E9%96%93">&#x8DDD;&#x96E2;&#x7A7A;&#x9593; - Wikipedia</a><br /> <a href="https://nemuneko-gensokyou.blog.so-net.ne.jp/2015-07-12-9">&#x7B2C;&#xFF16;&#x56DE; &#x8DDD;&#x96E2;&#x306E;&#x516C;&#x7406;&#xFF1A;&#x306D;&#x3080;&#x306D;&#x3053;&#x5E7B;&#x60F3;&#x90F7;&#xFF1A;So-net&#x30D6;&#x30ED;&#x30B0;</a><br /> <a href="https://dic.nicovideo.jp/a/%E8%B7%9D%E9%9B%A2">&#x8DDD;&#x96E2;&#x3068;&#x306F; (&#x30AD;&#x30E7;&#x30EA;&#x3068;&#x306F;) [&#x5358;&#x8A9E;&#x8A18;&#x4E8B;] - &#x30CB;&#x30B3;&#x30CB;&#x30B3;&#x5927;&#x767E;&#x79D1;</a></p><p> 数式で見ると難しく見えるかもしれませんが、この式はそれぞれ</p> <ul> <li>距離は負にはならない(非負性)</li> <li>同じ点同士の距離は0、距離が0の点は同じ点</li> <li>x,yの間の距離について、距離を測る起点を逆にしても距離は変わらない(対称性)</li> <li>x,zとまっすぐ行くときと比べて、yに寄り道すると必ずトータルの経路は長くなる(三角不等式)</li> </ul><p> ということを言っているだけなので、概念的には簡単です。</p><p> こういうものを満たすと距離と呼べる、ということですね。</p><p> 「コサイン距離」はどれを満たさないのでしょうか?</p> </div> <div class="section"> <h3>わからないの? ……ばか</h3> <p> 「コサイン距離」は2番目の<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20x%3Dy%5CLeftrightarrow%20d%28x%2C%20y%29%20%3D%200" alt=" x=y\Leftrightarrow d(x, y) = 0"/>と、4番目の三角不等式を満たしません。</p><p> 2番目を説明するのは簡単で、元のコサイン類似度はベクトル間の角度にしか興味を持たない性質があります。なので、たとえば二次元ベクトル<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%281%2C0%29" alt=" (1,0)"/>と<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%282%2C0%29" alt=" (2,0)"/>とか、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%2842%2C1%29" alt=" (42,1)"/>と<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%2884%2C2%29" alt=" (84,2)"/>は同じ距離になります。</p><p> 4番目については、反例を挙げてみましょう。</p><p><figure class="figure-image figure-image-fotolife" title="すごく単純な例"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190705/20190705023734.png" alt="&#x3059;&#x3054;&#x304F;&#x5358;&#x7D14;&#x306A;&#x4F8B;" title="f:id:hayataka2049:20190705023734p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>すごく単純な例</figcaption></figure></p><p> 特に凝ったことはしていません。この図において、単純なユークリッド距離を考えると、</p> <ul> <li>A-B間, B-C間の距離:1</li> <li>A-C間の距離:<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Csqrt%7B2%7D%5Csimeq%201.414" alt=" \sqrt{2}\simeq 1.414"/></li> </ul><p> となり、こういうのが三角不等式を満たしている場合です。A-B-Cとたどる経路の長さは2になるので、A-Cとたどるより長い距離をたどることになります。</p><p> では、「コサイン距離」では? というと、</p> <ul> <li>A-B間, B-C間の「コサイン距離」:約0.293</li> <li>A-C間の距離:1</li> </ul><p> となり、A-B-Cとたどることで約0.586になりますからA-Cと直接たどるより短い距離で行けてしまうことになります。つまり、三角不等式を満たさないので、「コサイン距離」は距離ではないということになります。</p> </div> <div class="section"> <h3>距離として扱うと困るのかって? ……困るに決まってるじゃないっ、わからずや!</h3> <p> データ分析などで、距離を使うことを前提としている手法で「コサイン距離」を使うと、不都合なことが起きる可能性があります。</p><p> みんなが大好きなirisのデータを多次元尺度構成法、MDSで可視化してみましょう。Pythonで書くとこうなります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> scipy.spatial.distance <span class="synPreProc">import</span> pdist, squareform <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris <span class="synPreProc">from</span> sklearn.manifold <span class="synPreProc">import</span> MDS <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): iris = load_iris() A = squareform(pdist(iris.data, <span class="synConstant">&quot;euclidean&quot;</span>)) mds = MDS(n_components=<span class="synConstant">2</span>, dissimilarity=<span class="synConstant">&quot;precomputed&quot;</span>, n_init=<span class="synConstant">10</span>, max_iter=<span class="synConstant">500</span>) X_2d = mds.fit_transform(A) <span class="synStatement">for</span> i, target <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(iris.target_names): mask = iris.target == i plt.scatter(X_2d[mask,<span class="synConstant">0</span>], X_2d[mask,<span class="synConstant">1</span>], label=target) plt.xlim(X_2d[:,<span class="synConstant">0</span>].min() - <span class="synConstant">1</span>, X_2d[:,<span class="synConstant">0</span>].max() + <span class="synConstant">1</span>) plt.ylim(X_2d[:,<span class="synConstant">1</span>].min() - <span class="synConstant">1</span>, X_2d[:,<span class="synConstant">1</span>].max() + <span class="synConstant">1</span>) plt.title(<span class="synConstant">&quot;MDS stress:{:.4f}&quot;</span>.format(mds.stress_)) plt.legend() plt.savefig(<span class="synConstant">&quot;iris_euclidean_mds.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p><figure class="figure-image figure-image-fotolife" title="iris_euclidean_mds.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190705/20190705025204.png" alt="iris_euclidean_mds.png" title="f:id:hayataka2049:20190705025204p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris_euclidean_mds.png</figcaption></figure></p><p> そんなに申し分はなさそうな結果ですね。</p><p> 「コサイン距離」でもやってみます。といっても、親切なことにscipyが「コサイン距離」を標準でサポートしているので、</p> <pre class="code lang-python" data-lang="python" data-unlink> A = squareform(pdist(iris.data, <span class="synConstant">&quot;cosine&quot;</span>)) </pre><p> とすれば一発でできます。あとはせいぜい出力ファイル名を変えておきます。<br /> (plt.savefig("iris_cosine_mds.png")としました。)</p><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.pdist.html">scipy.spatial.distance.pdist &mdash; SciPy v1.3.0 Reference Guide</a></p><p><figure class="figure-image figure-image-fotolife" title="iris_cosine_mds.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190705/20190705030217.png" alt="iris_cosine_mds.png" title="f:id:hayataka2049:20190705030217p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris_cosine_mds.png</figcaption></figure></p><p> なんかよくわからないことになりました。念のために中心付近にズームしてみます(plt.xlimとplt.ylimで調整)。</p><p><figure class="figure-image figure-image-fotolife" title="iris_cosine_mds_zoomed.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190705/20190705030238.png" alt="iris_cosine_mds_zoomed.png" title="f:id:hayataka2049:20190705030238p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>iris_cosine_mds_zoomed.png</figcaption></figure></p><p> 考えてみれば当然の結果で、コサイン類似度は-1から1のレンジを取ります。ということは、「コサイン距離」の最大値は2にしかならないのです。なので、遠い点が表現できなくなり、とても小さい範囲に押し込められます。</p><p> また、「コサイン距離」では向きが同じで長さの違うベクトル同士を区別できません。昔作ったirisの主成分分析のバイプロットを持ってくると、</p><p><figure class="figure-image figure-image-fotolife" title="irisのバイプロット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328230804.png" alt="iris&#x306E;&#x30D0;&#x30A4;&#x30D7;&#x30ED;&#x30C3;&#x30C8;" title="f:id:hayataka2049:20180328230804p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>irisのバイプロット</figcaption></figure></p><p><a href="https://www.haya-programming.com/entry/2018/03/28/231305">&#x3010;python&#x3011;python&#x3067;&#x4E3B;&#x6210;&#x5206;&#x5206;&#x6790;&#x306E;&#x30D0;&#x30A4;&#x30D7;&#x30ED;&#x30C3;&#x30C8; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> グループ間の差異は概ね第一主成分に、グループ内での差異は第二主成分にあらわれています。そして、第一主成分とほぼ同じ方向を向いている2つの変数、そうでもない2つの変数があることがわかります。</p><p> 品種が違うと各変数の相対的な比率が変わる反面、同じ品種同士では各変数の相対的な比率はさほど変わらない(全体的に大きかったり小さかったりという個体差があるだけ)と想定すれば、結果が一直線上に並ぶのもなんとなく理解できる気がします。</p> </div> <div class="section"> <h3>「じゃあどう呼べば良いのか」って? そんなの自分で考えてよね!</h3> <p> 「コサイン距離」に変わる呼称方法ですが……</p><p> ま、常識的に考えると、<b><span style="font-size: 150%">コサイン非類似度</span></b>でいいのではないでしょうか。</p> </div> <div class="section"> <h3>わかったなら感謝しなさい。……え、ありがとう? べ、べつに喜ばれても嬉しくなかんないんだからっ!</h3> <p> 安易に「コサイン距離」という言葉を使ってはいけないこと、また、距離として扱うと問題になるというか、イマイチな結果を招く可能性があることがこの記事でわかっていただけたら、嬉しいです。</p><p> あと、ツンデレ風の章タイトルにしたことに対して今更ながら後悔の念を感じ始めているのですが(自分で見返してもかなり痛い)、下書きに放り込んで一晩寝たらたぶん投稿する勇気がなくなっていると思うので、蛮勇を奮ってこのまま<s>後悔</s>公開することにします。</p> </div> Fri, 05 Jul 2019 03:09:35 +0900 hatenablog://entry/17680117127212914938 雑記 統計 ネタ・小ネタ 機械学習 データ前処理 自然言語処理 可視化 【python】ロジスティック回帰で確率値で学習させる https://www.haya-programming.com/entry/2019/06/30/220406 <div class="section"> <h3>はじめに</h3> <p> ロジスティック回帰は回帰という名前なのにほとんど二項判別に使われますが、たまに本当に回帰に使うときもあります。0.1とか0.4とか0.6のような目的変数を使ってモデルを作る、というケースです。</p><p> ちょっとした目的で必要になるかもしれないと思ってやろうとしたら、意外と手間取ったのでメモしておきます。</p> </div> <div class="section"> <h3>データ</h3> <p> たとえば「普及率」のようなデータに対してあてはめを行うとき、こういうケースが出てきます。</p><p> こちらで紹介されている、日本のカラーテレビ普及率のデータを使います。</p><p><a href="https://www1.doshisha.ac.jp/~mjin/R/Chap_16/16.html">&#x30C7;&#x30FC;&#x30BF;&#x89E3;&#x6790;&#x30FB;&#x30DE;&#x30A4;&#x30CB;&#x30F3;&#x30B0;&#x3068;R&#x8A00;&#x8A9E;</a></p><p> 説明変数が年、目的変数が普及率です。</p><p> とりあえずこんな配列にしておきます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np x = np.array([<span class="synConstant">1966</span>, <span class="synConstant">1967</span>, <span class="synConstant">1968</span>, <span class="synConstant">1969</span>, <span class="synConstant">1970</span>, <span class="synConstant">1971</span>, <span class="synConstant">1972</span>, <span class="synConstant">1973</span>, <span class="synConstant">1974</span>, <span class="synConstant">1975</span>, <span class="synConstant">1976</span>, <span class="synConstant">1977</span>, <span class="synConstant">1978</span>, <span class="synConstant">1979</span>, <span class="synConstant">1980</span>, <span class="synConstant">1981</span>, <span class="synConstant">1982</span>, <span class="synConstant">1983</span>, <span class="synConstant">1984</span>]).reshape(-<span class="synConstant">1</span>, <span class="synConstant">1</span>) y = np.array([<span class="synConstant">0.003</span>, <span class="synConstant">0.016</span>, <span class="synConstant">0.054</span>, <span class="synConstant">0.139</span>, <span class="synConstant">0.263</span>, <span class="synConstant">0.423</span>, <span class="synConstant">0.611</span>, <span class="synConstant">0.758</span>, <span class="synConstant">0.859</span>, <span class="synConstant">0.903</span>, <span class="synConstant">0.937</span>, <span class="synConstant">0.954</span>, <span class="synConstant">0.978</span>, <span class="synConstant">0.978</span>, <span class="synConstant">0.982</span>, <span class="synConstant">0.985</span>, <span class="synConstant">0.989</span>, <span class="synConstant">0.988</span>, <span class="synConstant">0.992</span>]) </pre> </div> <div class="section"> <h3>scikit-learnでは(たぶん)できない</h3> <p> 誰でもまっさきに思いつく方法は、sklearnのLogisticRegressionを使うことです。しかし、これは</p> <blockquote> <p>Logistic Regression (aka logit, MaxEnt) classifier.</p><p><a href="https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html">sklearn.linear_model.LogisticRegression &mdash; scikit-learn 0.21.2 documentation</a></p> </blockquote> <p> と書いてあるとおり、判別用のモデルです。ユーザガイドもひたすら判別の話をしているだけです。</p><p><a href="https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression">1.1. Generalized Linear Models &mdash; scikit-learn 0.21.2 documentation</a></p><p> まあでも、もしかしたらできるかもしれないので、やってみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LogisticRegression x = np.array([<span class="synConstant">1966</span>, <span class="synConstant">1967</span>, <span class="synConstant">1968</span>, <span class="synConstant">1969</span>, <span class="synConstant">1970</span>, <span class="synConstant">1971</span>, <span class="synConstant">1972</span>, <span class="synConstant">1973</span>, <span class="synConstant">1974</span>, <span class="synConstant">1975</span>, <span class="synConstant">1976</span>, <span class="synConstant">1977</span>, <span class="synConstant">1978</span>, <span class="synConstant">1979</span>, <span class="synConstant">1980</span>, <span class="synConstant">1981</span>, <span class="synConstant">1982</span>, <span class="synConstant">1983</span>, <span class="synConstant">1984</span>]).reshape(-<span class="synConstant">1</span>, <span class="synConstant">1</span>) y = np.array([<span class="synConstant">0.003</span>, <span class="synConstant">0.016</span>, <span class="synConstant">0.054</span>, <span class="synConstant">0.139</span>, <span class="synConstant">0.263</span>, <span class="synConstant">0.423</span>, <span class="synConstant">0.611</span>, <span class="synConstant">0.758</span>, <span class="synConstant">0.859</span>, <span class="synConstant">0.903</span>, <span class="synConstant">0.937</span>, <span class="synConstant">0.954</span>, <span class="synConstant">0.978</span>, <span class="synConstant">0.978</span>, <span class="synConstant">0.982</span>, <span class="synConstant">0.985</span>, <span class="synConstant">0.989</span>, <span class="synConstant">0.988</span>, <span class="synConstant">0.992</span>]) lr = LogisticRegression() lr.fit(x, y) <span class="synComment"># =&gt; ValueError: Unknown label type: 'continuous'</span> lr.predict(x) </pre><p> しってた。</p> </div> <div class="section"> <h3>statsmodelsでやる</h3> <p> 仕方がないので、statsmodelsを使います。statsmodelsはPythonでR風のことをするためのライブラリなので、参考ページと同じことができるはずです。</p><p> APIをぜんぜん把握していないので、qiitaの解説記事を見ながらやります。</p><p><a href="https://qiita.com/TomokIshii/items/374ac7d4231adf6a39f4">Statsmodels &#x3067;&#x30ED;&#x30B8;&#x30B9;&#x30C6;&#x30A3;&#x30C3;&#x30AF;&#x56DE;&#x5E30;&#x3092;&#x884C;&#x3046;&#x969B;&#x306E;&#x6CE8;&#x610F;&#x70B9; - Qiita</a><br /> </p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> statsmodels.api <span class="synStatement">as</span> sm <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt x = np.array([<span class="synConstant">1966</span>, <span class="synConstant">1967</span>, <span class="synConstant">1968</span>, <span class="synConstant">1969</span>, <span class="synConstant">1970</span>, <span class="synConstant">1971</span>, <span class="synConstant">1972</span>, <span class="synConstant">1973</span>, <span class="synConstant">1974</span>, <span class="synConstant">1975</span>, <span class="synConstant">1976</span>, <span class="synConstant">1977</span>, <span class="synConstant">1978</span>, <span class="synConstant">1979</span>, <span class="synConstant">1980</span>, <span class="synConstant">1981</span>, <span class="synConstant">1982</span>, <span class="synConstant">1983</span>, <span class="synConstant">1984</span>]).reshape(-<span class="synConstant">1</span>, <span class="synConstant">1</span>) y = np.array([<span class="synConstant">0.003</span>, <span class="synConstant">0.016</span>, <span class="synConstant">0.054</span>, <span class="synConstant">0.139</span>, <span class="synConstant">0.263</span>, <span class="synConstant">0.423</span>, <span class="synConstant">0.611</span>, <span class="synConstant">0.758</span>, <span class="synConstant">0.859</span>, <span class="synConstant">0.903</span>, <span class="synConstant">0.937</span>, <span class="synConstant">0.954</span>, <span class="synConstant">0.978</span>, <span class="synConstant">0.978</span>, <span class="synConstant">0.982</span>, <span class="synConstant">0.985</span>, <span class="synConstant">0.989</span>, <span class="synConstant">0.988</span>, <span class="synConstant">0.992</span>]) x_c = sm.add_constant(x) <span class="synComment"># ↑interceptのためにやらないといけないらしい(えぇ…)</span> lr = sm.Logit(y, x_c) lr_result = lr.fit() <span class="synIdentifier">print</span>(lr_result.params) <span class="synIdentifier">print</span>(lr_result.summary()) y_pred = lr.predict(lr_result.params) plt.scatter(x.ravel(), y_pred, c=<span class="synConstant">&quot;b&quot;</span>, alpha=<span class="synConstant">0.2</span>) plt.plot(x.ravel(), y_pred, c=<span class="synConstant">&quot;b&quot;</span>) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) </pre><p> y, xって書くのがきもいとか、どうでもいいところが気になります。</p><p> これはこれでRに慣れてる人にはいいと思うのですが、scikit-learnライクなAPIも用意してくれていたらと思わなくはありません。</p><p> 結果</p> <pre class="code" data-lang="" data-unlink>Optimization terminated successfully. Current function value: 0.180377 Iterations 9 [-1.23730786e+03 6.27547565e-01] Logit Regression Results ============================================================================== Dep. Variable: y No. Observations: 19 Model: Logit Df Residuals: 17 Method: MLE Df Model: 1 Date: Sun, 30 Jun 2019 Pseudo R-squ.: 0.7003 Time: 22:00:31 Log-Likelihood: -3.4272 converged: True LL-Null: -11.435 Covariance Type: nonrobust LLR p-value: 6.284e-05 ============================================================================== coef std err z P&gt;|z| [0.025 0.975] ------------------------------------------------------------------------------ const -1237.3079 591.413 -2.092 0.036 -2396.456 -78.159 x1 0.6275 0.300 2.092 0.036 0.040 1.215 ==============================================================================</pre><p><figure class="figure-image figure-image-fotolife" title="結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190630/20190630220216.png" alt="&#x7D50;&#x679C;" title="f:id:hayataka2049:20190630220216p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>結果</figcaption></figure></p><p> なんかよくわからないけど、一応できているみたいです。</p> </div> <div class="section"> <h3>まとめ</h3> <p> いろいろ分析していくとstatsmodelsもけっきょく必要になるときがあるので、慣れた方が良いのかなぁと思ったりしました。</p> </div> Sun, 30 Jun 2019 22:04:06 +0900 hatenablog://entry/17680117127211260384 python statsmodels Tips 統計 ロジスティック回帰 【python】statsmodelsでt検定する方法 https://www.haya-programming.com/entry/2019/05/24/005837 <div class="section"> <h3>はじめに</h3> <p> この前はscipyでやる方法をまとめたわけですが、</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F05%2F17%2F223849" title="【python】scipyでt検定する方法まとめ - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/05/17/223849">&#x3010;python&#x3011;scipy&#x3067;t&#x691C;&#x5B9A;&#x3059;&#x308B;&#x65B9;&#x6CD5;&#x307E;&#x3068;&#x3081; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><br /> <p> 片側のオプションほしいなーと思ったのでstatsmodelsに浮気することにしました。</p> </div> <div class="section"> <h3>使い方の概要</h3> <p> 対応のないt検定はこれです。</p><p><a href="https://www.statsmodels.org/stable/generated/statsmodels.stats.weightstats.ttest_ind.html?highlight=ttest_ind">statsmodels.stats.weightstats.ttest_ind &mdash; statsmodels v0.10.1 documentation</a></p><p> 引数は以下のようなものです。</p> <blockquote> <p>statsmodels.stats.weightstats.ttest_ind(x1, x2,<br /> alternative='two-sided', usevar='pooled', weights=(None, None), value=0)</p> </blockquote> <p> x1とx2が検定対象です。オプションで重要そうなのはalternativeとusevarです。</p> <ul> <li>alternative</li> </ul><p> "two-sided"と"larger"と"smaller"が渡せます。後ろの2つは片側のオプションです。</p><p> こうしちゃうと、どっちがどっちに対して大きい/小さいのかわからん、とか考えないのかしら(x1 larger/smaller than x2(x1はx2より大きい/小さい)です。)。</p> <ul> <li>usevar</li> </ul><p> "pooled"と"unequal"が渡せます。スチューデントのt検定とウェルチのt検定に対応しているはずです。</p><p> 難しくはないけど、いちいちstrで重要なオプションを渡さないといけないのは少し面倒ですかね。間違った値にするとエラー出してくれるみたいなので、そういう意味では親切ですが。</p> </div> <div class="section"> <h3>実際にやってみる</h3> <p> 完全に前回と同じノリでやります。</p><p> 分布をとりあえず作る。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> scipy <span class="synPreProc">import</span> stats &gt;&gt;&gt; <span class="synPreProc">from</span> statsmodels.stats.weightstats <span class="synPreProc">import</span> ttest_ind &gt;&gt;&gt; d1 = stats.norm(loc=<span class="synConstant">0</span>, scale=<span class="synConstant">1</span>) &gt;&gt;&gt; d2 = stats.norm(loc=<span class="synConstant">1</span>, scale=<span class="synConstant">1</span>) </pre><p> やる。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; a = d1.rvs(<span class="synConstant">3</span>) &gt;&gt;&gt; b = d2.rvs(<span class="synConstant">3</span>) &gt;&gt;&gt; a array([ <span class="synConstant">0.90831032</span>, -<span class="synConstant">0.88621515</span>, <span class="synConstant">0.09060862</span>]) &gt;&gt;&gt; b array([<span class="synConstant">1.87384828</span>, <span class="synConstant">2.92258811</span>, <span class="synConstant">0.8539749</span> ]) &gt;&gt;&gt; ttest_ind(a, b) (-<span class="synConstant">2.333627132285731</span>, <span class="synConstant">0.07993392828229898</span>, <span class="synConstant">4.0</span>) </pre><p> t統計量、p値、自由度が返るみたいですね。</p><p> ここで仮に有意水準0.05とすると、もう少しでいけそうだったけど切れなかったという残念な例です(そうなるまで何回か回しました)。そこで、片側検定にしてみます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; ttest_ind(a, b, alternative=<span class="synConstant">&quot;smaller&quot;</span>) (-<span class="synConstant">2.333627132285731</span>, <span class="synConstant">0.03996696414114949</span>, <span class="synConstant">4.0</span>) </pre><p> 教科書通り半分のp値になって、めでたく「有意差」が出ました。教科書には「こういうことはやるな」と書いてあると思います。気をつけましょう。</p><p> なお、片側の方向を間違えて指定した場合は、</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; ttest_ind(a, b, alternative=<span class="synConstant">&quot;larger&quot;</span>) (-<span class="synConstant">2.333627132285731</span>, <span class="synConstant">0.9600330358588506</span>, <span class="synConstant">4.0</span>) </pre><p> だいたい大きいp値になるので気づきます。でも、元のp値が0.42とかだったりすると案外気づかないかもしれない(どっちにしろ有意差ではないし、実害ないかもですが・・・)。</p> </div> <div class="section"> <h3>まとめ</h3> <p> こっちの方が高機能だし、これでいいんじゃ? という感じもします。でもscipy入れててもstatsmodels入れてない人は多いと思うので、微妙っちゃ微妙ですね。</p> </div> Fri, 24 May 2019 00:58:37 +0900 hatenablog://entry/17680117127161793457 python 統計 Tips statsmodels 【python】scipyでt検定する方法まとめ https://www.haya-programming.com/entry/2019/05/17/223849 <div class="section"> <h3 id="概要">概要</h3> <p> いっっっつも使い方を忘れて調べているので、自分で備忘録を書いておくことにしました。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a></li> <li><a href="#t検定の概要">t検定の概要</a></li> <li><a href="#使う関数">使う関数</a></li> <li><a href="#引数と注意点">引数と注意点</a></li> <li><a href="#やってみる">やってみる</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#statsmodelsでやる方法">statsmodelsでやる方法</a></li> </ul> </div> <div class="section"> <h3 id="t検定の概要">t検定の概要</h3> <p> t検定と一口に言っても実際はいろいろありますが、今回やるのは2群の標本の平均に差異があるかどうかの検定です。帰無仮説は「両者の平均に差はない」、対立仮説は「両者の平均に差がある」です。</p><p> 詳しいことはwikipediaとかを見てください(手抜き)。</p><p><a href="https://ja.wikipedia.org/wiki/T%E6%A4%9C%E5%AE%9A">t&#x691C;&#x5B9A; - Wikipedia</a></p><p></p> </div> <div class="section"> <h3 id="使う関数">使う関数</h3> <p> scipyのt検定を行う関数としては、</p> <ul> <li>scipy.stats.ttest_ind</li> <li>scipy.stats.ttest_rel</li> </ul><p> の2つがあります。ttest_indは対応のないt検定、ttest_relは対応のあるt検定で使えます。</p><p> 使い所が多いのは対応のないt検定を行うttest_indの方なので、こちらだけ取り扱います。</p> </div> <div class="section"> <h3 id="引数と注意点">引数と注意点</h3> <p> いろいろあります。</p> <pre class="code" data-lang="" data-unlink>scipy.stats.ttest_ind(a, b, axis=0, equal_var=True, nan_policy=&#39;propagate&#39;)</pre><p> a,bは普通にデータの入った1次元配列を渡して使うことが多いでしょう。axisという引数があることから想像が付く通り、多次元配列でも渡せるようです。使ったことはありません。</p><p> equal_var=Trueだとスチューデントのt検定、equal=var=Falseだとウェルチのt検定です。これは等分散かどうかに関わらずウェルチのt検定で良いという話題があるので、Falseを指定してやると良いと思います。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fynakayama%2Fitems%2Fb9ec31a296de48e62863" title="等分散か否かに関わらずウェルチの t 検定を使う (べきである) - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://qiita.com/ynakayama/items/b9ec31a296de48e62863">&#x7B49;&#x5206;&#x6563;&#x304B;&#x5426;&#x304B;&#x306B;&#x95A2;&#x308F;&#x3089;&#x305A;&#x30A6;&#x30A7;&#x30EB;&#x30C1;&#x306E; t &#x691C;&#x5B9A;&#x3092;&#x4F7F;&#x3046; (&#x3079;&#x304D;&#x3067;&#x3042;&#x308B;) - Qiita</a></p><p> 他の引数はあまり重要ではないので、説明を省略します。</p><p> 結果は(t統計量, p値)というtupleっぽいオブジェクト<a href="#f-40d50157" name="fn-40d50157" title="厳密にはTtest_indResultという型だが、tupleの派生クラスで実質的にtupleとして扱えるので気にしなくて良い。中身はともにfloat">*1</a>で返ります。p値が設定した有意水準(たとえば0.05)より<span style="font-size: 150%"><b>小さい</b></span>ときに有意差があったと言えます(不慣れだと毎回「どっちだっけ?」と思うポイント)。</p><p> また気になる点として、t検定は母集団が正規分布に従うというけっこうきつい仮定を置いています。しかし、実はあまり気にする必要はないという議論もあります。</p> <blockquote> <p>実際には <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20X" alt=" X"/> が正規分布でなくても,<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20n" alt=" n"/> が大きければ中心極限定理により <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Coverline%7BX%7D" alt=" \overline{X}"/> は正規分布に近づくので,この検定は母集団が正規分布かどうかには鈍感です。データの分布が正規分布かどうかの検定をしてから t検定を行う必要はまったくありません。</p><p><a href="https://oku.edu.mie-u.ac.jp/~okumura/stat/ttest.html">t&#x691C;&#x5B9A;</a></p> </blockquote> <p> そういうことらしいです。</p><p> なお、scipyのt検定に片側検定のオプションはありません。両側検定の結果から計算するか、他のライブラリ(statmodelsなど)でやることになります。両側検定の結果から計算する場合は、</p> <blockquote> <pre class="code lang-python" data-lang="python" data-unlink>t, p = stats.ttest_ind(male, female, equal_var=<span class="synIdentifier">True</span>) pval3 = p pval2 = p / <span class="synConstant">2.0</span> pval1 = <span class="synConstant">1.0</span> - pval2 <span class="synStatement">if</span> t &lt; <span class="synConstant">0.0</span>: w = pval2 pval2 = pval1 pval1 = w </pre><p><a href="http://cup.sakura.ne.jp/pandas/pandas03.htm">python&#x306E;pandas&#x306B;&#x3088;&#x308B;&#x7C21;&#x5358;&#x306A;&#x7D71;&#x8A08;&#x51E6;&#x7406;&#xFF1A;&#x7B2C;&#xFF13;&#x56DE; F&#x691C;&#x5B9A;&#xFF0C;t&#x691C;&#x5B9A;&#x305D;&#x306E;&#x4ED6;</a><br /> </p> </blockquote> <p> みたいな感じになるようです。</p> </div> <div class="section"> <h3 id="やってみる">やってみる</h3> <p> まず適当な確率分布のオブジェクトを生成する。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> scipy <span class="synPreProc">import</span> stats &gt;&gt;&gt; d1 = stats.norm(loc=<span class="synConstant">0</span>, scale=<span class="synConstant">1</span>) &gt;&gt;&gt; d2 = stats.norm(loc=<span class="synConstant">1</span>, scale=<span class="synConstant">1</span>) </pre><p> N(0, 1)とN(1, 1)です。</p><p> 標本はこんな感じで取れます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; d1.rvs(<span class="synConstant">10</span>) array([ <span class="synConstant">0.18711344</span>, <span class="synConstant">0.3534579</span> , -<span class="synConstant">0.52046706</span>, <span class="synConstant">0.47855615</span>, -<span class="synConstant">0.51033227</span>, <span class="synConstant">0.70266909</span>, <span class="synConstant">0.19253524</span>, <span class="synConstant">0.28232341</span>, <span class="synConstant">1.24373963</span>, -<span class="synConstant">0.70771188</span>]) </pre><p> 参考:<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F12%2F02%2F035608" title="scipyで確率分布のサンプルと確率密度関数を生成する - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2018/12/02/035608">scipy&#x3067;&#x78BA;&#x7387;&#x5206;&#x5E03;&#x306E;&#x30B5;&#x30F3;&#x30D7;&#x30EB;&#x3068;&#x78BA;&#x7387;&#x5BC6;&#x5EA6;&#x95A2;&#x6570;&#x3092;&#x751F;&#x6210;&#x3059;&#x308B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> 念のために正しいパラメータになっているか確かめます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; np.mean(d1.rvs(<span class="synConstant">1000</span>)) <span class="synConstant">0.031232377764520782</span> &gt;&gt;&gt; np.var(d1.rvs(<span class="synConstant">1000</span>)) <span class="synConstant">0.9921694887020086</span> &gt;&gt;&gt; np.mean(d2.rvs(<span class="synConstant">1000</span>)) <span class="synConstant">0.97389464006143</span> &gt;&gt;&gt; np.var(d2.rvs(<span class="synConstant">1000</span>)) <span class="synConstant">1.0268883977332324</span> </pre><p> 大丈夫そうなのでt検定します。有意水準0.05とします(先に決めるのがルールなので・・・)。</p><p> 最初は標本サイズ3でやってみます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; a = d1.rvs(<span class="synConstant">3</span>) &gt;&gt;&gt; b = d2.rvs(<span class="synConstant">3</span>) &gt;&gt;&gt; a array([-<span class="synConstant">1.29621283</span>, <span class="synConstant">0.42129238</span>, -<span class="synConstant">0.13701242</span>]) &gt;&gt;&gt; b array([ <span class="synConstant">0.81419163</span>, <span class="synConstant">1.21399486</span>, -<span class="synConstant">1.40737252</span>]) &gt;&gt;&gt; stats.ttest_ind(a, b, equal_var=<span class="synIdentifier">False</span>) Ttest_indResult(statistic=-<span class="synConstant">0.5672127490081906</span>, pvalue=<span class="synConstant">0.6064712381602329</span>) </pre><p> pvalue=0.6064712381602329で、0.05より圧倒的におおきいので有意差なしということになります。サンプルサイズが少なすぎて有意差が出せないのです。</p><p> 10まで増やしてみます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; c = d1.rvs(<span class="synConstant">10</span>) &gt;&gt;&gt; d = d2.rvs(<span class="synConstant">10</span>) &gt;&gt;&gt; stats.ttest_ind(c, d, equal_var=<span class="synIdentifier">False</span>) Ttest_indResult(statistic=-<span class="synConstant">2.8617115251996275</span>, pvalue=<span class="synConstant">0.011903486818782736</span>) </pre><p> 今度は出ました。ただし何回かやると有意になったりならなかったりするので、出方のばらつき次第で変わる可能性があります。</p><p> サンプルサイズの見積もりは以下の方法があるそうです。</p> <blockquote> <p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20n%3D16%5Cfrac%7Bs%5E2%7D%7Bd%5E2%7D" alt=" n=16\frac{s^2}{d^2}"/><br /> <a href="https://bellcurve.jp/statistics/blog/14304.html">&#x5E7E;&#x3064;&#x30C7;&#x30FC;&#x30BF;&#x304C;&#x5FC5;&#x8981;&#x304B;&#xFF1F;&#x2015;&#x5E73;&#x5747;&#x5024;&#x306E;&#x5DEE;&#x306E;&#x691C;&#x5B9A; | &#x30D6;&#x30ED;&#x30B0; | &#x7D71;&#x8A08;WEB</a><br />  ※引用者注:<br />  <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20s" alt=" s"/>は標準偏差、<img src="https://chart.apis.google.com/chart?cht=tx&chl=d" alt="d"/>は期待される二群間の差<br />  上式は有意水準5%の設定の場合に80%の検出力になるサンプル数<br />  (詳細はリンク先で読んでください)</p> </blockquote> <p> 今回は標準偏差、二群間の差ともに1という簡単な設定なので、16サンプルあれば良い計算です。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synIdentifier">sum</span>(stats.ttest_ind( ... d1.rvs(<span class="synConstant">16</span>), d2.rvs(<span class="synConstant">16</span>), equal_var=<span class="synIdentifier">False</span>)[<span class="synConstant">1</span>] &lt; <span class="synConstant">0.05</span> ... <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">100</span>)) <span class="synConstant">78</span> </pre><p> よさそうです。</p> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> 簡単なことなのですが、割とやり方を忘れやすいので書き記しました。これで今後は忘れないで済むでしょう(フラグ)。</p> </div> <div class="section"> <h3 id="statsmodelsでやる方法">statsmodelsでやる方法</h3> <p> statsmodelsでもできるので、こちらの記事も参考にしてください。片側検定が簡単にできるなどのメリットがあります。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2019%2F05%2F24%2F005837" title="【python】statsmodelsでt検定する方法 - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2019/05/24/005837">&#x3010;python&#x3011;statsmodels&#x3067;t&#x691C;&#x5B9A;&#x3059;&#x308B;&#x65B9;&#x6CD5; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p> </div><div class="footnote"> <p class="footnote"><a href="#fn-40d50157" name="f-40d50157" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">厳密にはTtest_indResultという型だが、tupleの派生クラスで実質的にtupleとして扱えるので気にしなくて良い。中身はともにfloat</span></p> </div> Fri, 17 May 2019 22:38:49 +0900 hatenablog://entry/17680117127139100465 python scipy 統計 Tips 【python】scipyで階層型クラスタリングするときの知見まとめ https://www.haya-programming.com/entry/2019/02/11/035943 <div class="section"> <h3 id="はじめに">はじめに</h3> <p> scipyの階層型クラスタリングを使う機会がありましたが、使い方がわかりづらいと思ったのでまとめておきます。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#関数がいっぱいある">関数がいっぱいある</a></li> <li><a href="#使い方">使い方</a><ul> <li><a href="#linkage">linkage</a></li> <li><a href="#fcluster">fcluster</a></li> <li><a href="#cophenet">cophenet</a></li> <li><a href="#dendrogram">dendrogram</a></li> </ul> </li> <li><a href="#実践編">実践編</a><ul> <li><a href="#データを作る">データを作る</a></li> <li><a href="#手法を選ぶ">手法を選ぶ</a></li> </ul> </li> <li><a href="#クラスタに分ける">クラスタに分ける</a><ul> <li><a href="#デンドログラムを描く">デンドログラムを描く</a></li> <li><a href="#遊ぶ">遊ぶ</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> </div> <div class="section"> <h3 id="関数がいっぱいある">関数がいっぱいある</h3> <p> いっぱいあるんですよ。</p><p><a href="https://docs.scipy.org/doc/scipy/reference/cluster.hierarchy.html">Hierarchical clustering (scipy.cluster.hierarchy) &mdash; SciPy v1.3.0 Reference Guide</a></p><p> 私の数え間違えがなければ31個。多いですね。</p><p> とはいえ、本質的なもの(実際によく使うもの)は以下くらいです。</p> <ul> <li>linkage</li> </ul><p> 実際に階層型クラスタリングを行う。これがないと始まらない。</p> <ul> <li>fcluster</li> </ul><p> 任意の深さで木を切り、クラスタに分割する。</p> <ul> <li>cophenet</li> </ul><p> yを渡すとコーフェン相関係数なる評価指標を出してくれるらしい。</p> <ul> <li>dendrogram</li> </ul><p> デンドログラムを描画する。</p><p> 他に、こんな関数があります。</p> <ul> <li>fclusterdata, leaders</li> </ul><p> fclusterの処理と絡む便利関数。</p> <ul> <li>single, complet , average,...など</li> </ul><p> クラスタリングアルゴリズムに対応。すべてlinkageの引数で文字列を使って指定できるので実際にこれらの関数を使うことはない。</p><p> 他にも各種の数値を出せたり、MATLABのフォーマットと相互変換してくれたり、ポインタのリンクで表現された構造化データに変換するクラスだったり、いろいろなものが押し込まれています。必要に応じてリファレンスから探してくれば良いので、それぞれ述べることはしません。</p> </div> <div class="section"> <h3 id="使い方">使い方</h3> <p> とりあえず上で挙げた実際によく使う関数を中心に説明していきますよ。</p><p><span style="font-size: 80%">スポンサーリンク</span><br /> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p> <p><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-6261827798827777" data-ad-slot="1744230936" data-ad-format="auto" data-full-width-responsive="true"></ins><br /> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script></p><p></p> <div class="section"> <h4 id="linkage">linkage</h4> <p> データをlinkageに通すことで階層型クラスタリングが行えます。返り値として木の情報を表す配列が返ります。それに対して用意されている関数であれこれ処理していくというのが基本的な流れです。</p> <blockquote> <pre class="code" data-lang="" data-unlink>scipy.cluster.hierarchy.linkage(y, method=&#39;single&#39;, metric=&#39;euclidean&#39;, optimal_ordering=False</pre><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html#scipy.cluster.hierarchy.linkage">scipy.cluster.hierarchy.linkage &mdash; SciPy v1.3.0 Reference Guide</a></p> </blockquote> <p> yはデータですが、基本的にはscipy.spatial.distance.pdistで作れる距離行列のフォーマット(一般的な正方距離行列とは異なるので注意)で渡してくれ、ということになっています。</p><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.pdist.html">scipy.spatial.distance.pdist &mdash; SciPy v1.3.0 Reference Guide</a></p><p> でもshape=(サンプル数, ベクトル次元数)のnumpy配列も受け取ってくれるので、それほど気を配る必要はありません。</p><p> 気を配らないといけないのはむしろ距離行列を入れてクラスタリングしたい場合で、その場合は正方行列として入れようとすると正しく処理されません。pdistのフォーマットに変換する必要があります。これはscipy.spatial.distance.squareformで変換できるので、そうしてください。</p><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.squareform.html">scipy.spatial.distance.squareform &mdash; SciPy v1.3.0 Reference Guide</a></p><p> あとは距離(距離行列で入力した場合は無視される)とクラスタリング方法を指定できます。選べるものの一覧はリファレンスを見てください。選択肢はいろいろ実装されているので、ここで「欲しいものがなくて困る」というシチュエーションはそう滅多にないと思います。</p><p> linkageの返り値はnumpy配列です。細かいフォーマットについてはリファレンスを(ry。自分でこれをいじってどうこうしようという機会はあまりないと思うので、知らなくてもなんとなく使えます。リファレンスの説明ではだいたいZという変数に代入しているので、それに倣うと良いと思います。</p> </div> <div class="section"> <h4 id="fcluster">fcluster</h4> <p> クラスタリングしてくれます。</p> <blockquote> <pre class="code" data-lang="" data-unlink>scipy.cluster.hierarchy.fcluster(Z, t, criterion=&#39;inconsistent&#39;, depth=2, R=None, monocrit=None)</pre><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.fcluster.html">scipy.cluster.hierarchy.fcluster &mdash; SciPy v1.3.0 Reference Guide</a></p> </blockquote> <p> いちいち細かく述べることはしませんが、Zにlinkageの返り値を入れ、tはスレッショルドでクラスタの分割/結合の基準として渡します。あとはcriterionに好きなアルゴリズムを選ぶ……という仕組みですね。</p> </div> <div class="section"> <h4 id="cophenet">cophenet</h4> <p> 「cophenetic distances」なるものを計算します。</p> <blockquote> <pre class="code" data-lang="" data-unlink>scipy.cluster.hierarchy.cophenet(Z, Y=None)</pre><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.cophenet.html">scipy.cluster.hierarchy.cophenet &mdash; SciPy v1.3.0 Reference Guide</a></p> </blockquote> <p> <br />  Y(クラスタリングの元になった距離行列。当然pdistフォーマット)を渡すとクラスタリング全体の評価指標を出してくれるので、その目的で使うのが良いと思います。返り値は2つあり得るんですが、Yを渡さなければ1つめ(全体の評価指標)は省略されて2つめだけ返ります。</p><p> MATLABから輸入された関数らしく、MATLABのドキュメントを読んだ方がわかりやすいと思います。</p><p><a href="https://jp.mathworks.com/help/stats/cophenet.html">&#x30B3;&#x30FC;&#x30D5;&#x30A7;&#x30F3;&#x76F8;&#x95A2;&#x4FC2;&#x6570; - MATLAB cophenet - MathWorks &#x65E5;&#x672C;</a></p><p> あとで書く実践編でちょっと触れます。</p> </div> <div class="section"> <h4 id="dendrogram">dendrogram</h4> <p> デンドログラムを描きます。デンドログラム出しとけばとりあえず納得感があるので、これだけ出してみてからあれこれすれば良いと思います。</p> <blockquote> <pre class="code" data-lang="" data-unlink>scipy.cluster.hierarchy.dendrogram(Z, p=30, truncate_mode=None, color_threshold=None, get_leaves=True, orientation=&#39;top&#39;, labels=None, count_sort=False, distance_sort=False, show_leaf_counts=True, no_plot=False, no_labels=False, leaf_font_size=None, leaf_rotation=None, leaf_label_func=None, show_contracted=False, link_color_func=None, ax=None, above_threshold_color=&#39;b&#39;)</pre><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.dendrogram.html">scipy.cluster.hierarchy.dendrogram &mdash; SciPy v1.3.0 Reference Guide</a></p> </blockquote> <p> 引数が泡吹いて倒れるほどいっぱいある。到底説明なんてできないので、代表的なものだけピックアップします。</p> <ul> <li>Z</li> </ul><p> 説明不要。linkageの結果を渡す。</p> <ul> <li>p</li> </ul><p> 木を省略して表示するときのパラメータ。次のtruncate_modeと絡みます。</p> <ul> <li>truncate_mode</li> </ul><p> どのように木を省略するか。これはデータ数がある程度多いときに威力を発揮します。今回は使いません。</p> <ul> <li>color_threshold </li> </ul><p>  色分けに絡むしきい値。木の中の最大ノード間距離との比率で色分けを決めます。</p> <ul> <li>labels</li> </ul><p> 葉のラベル。</p> <ul> <li>ax</li> </ul><p> matplotlibのAxesを渡せます。渡すとそこに描画されます。</p><p> あとはまあ、いろいろ。基本的にはどれも表示フォーマットに絡む引数なので、見た目を変えたくなったら合う引数を探すという感じです。</p> </div> </div> <div class="section"> <h3 id="実践編">実践編</h3> <p> ではこれから実践してみます。</p> <div class="section"> <h4 id="データを作る">データを作る</h4> <p> 階層型クラスタリングは50>データ数くらいが適しています。多くても見づらいし計算量が嵩むからです。これくらいでのサイズの使いやすいデータはすぐに思い浮かびませんでしたが、sklearnのdigitsでラベルごとに平均を取ると良さげなことに気づいたので、そうします。</p><p> ついでに可視化もする。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_digits <span class="synStatement">def</span> <span class="synIdentifier">gen_data</span>(): digits = load_digits() label_uniq = np.unique(digits.target) result = [] <span class="synStatement">for</span> label <span class="synStatement">in</span> label_uniq: result.append(digits.data[digits.target == label].mean(axis=<span class="synConstant">0</span>)) <span class="synStatement">return</span> result, label_uniq <span class="synStatement">def</span> <span class="synIdentifier">visualize</span>(): X, y = gen_data() fig, axes = plt.subplots(nrows=<span class="synConstant">2</span>, ncols=<span class="synConstant">5</span>) <span class="synStatement">for</span> ax, x, label <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(axes.ravel(), X, y): ax.set_title(label) ax.imshow(x.reshape(<span class="synConstant">8</span>, <span class="synConstant">8</span>)) plt.savefig(<span class="synConstant">&quot;data.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: visualize() </pre><p><figure class="figure-image figure-image-fotolife" title="data.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190211/20190211021826.png" alt="data.png" title="f:id:hayataka2049:20190211021826p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>data.png</figcaption></figure></p><p> よさげなので、この10件のデータでやってみます。</p> </div> <div class="section"> <h4 id="手法を選ぶ">手法を選ぶ</h4> <p> とりあえずmetric="euclidean"に固定してmethodを変化させ、評価指標を出してみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> scipy.spatial.distance <span class="synPreProc">import</span> pdist <span class="synPreProc">from</span> scipy.cluster.hierarchy <span class="synPreProc">import</span> linkage, cophenet <span class="synStatement">def</span> <span class="synIdentifier">clustering_score</span>(): X, y = gen_data() methods = [<span class="synConstant">&quot;single&quot;</span>, <span class="synConstant">&quot;complete&quot;</span>, <span class="synConstant">&quot;average&quot;</span>, <span class="synConstant">&quot;weighted&quot;</span>, <span class="synConstant">&quot;centroid&quot;</span>, <span class="synConstant">&quot;median&quot;</span>, <span class="synConstant">&quot;ward&quot;</span>] <span class="synStatement">for</span> method <span class="synStatement">in</span> methods: S = pdist(X) Z = linkage(S, method=method) c, d = cophenet(Z, S) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;{0} {1:.3f}&quot;</span>.format(method, c)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: clustering_score() </pre><pre class="code" data-lang="" data-unlink>single 0.722 complete 0.752 average 0.769 weighted 0.766 centroid 0.681 median 0.730 ward 0.720</pre><p> average(UPGMA、いわゆる群平均法)がベストっぽいので、これを使うことにします。</p><p> どれを選んでもだいたい0.75くらいのコーフェン相関係数なので、25%くらいはデータの性質が狂っていると思って結果を解釈する必要がある、ということだと思います。あまり細かいところを見ても無意味です。</p> </div> </div> <div class="section"> <h3 id="クラスタに分ける">クラスタに分ける</h3> <p> fclusterを使うと適当な数のクラスタに分割できます。4つのクラスタに分割してみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> collections <span class="synPreProc">import</span> defaultdict <span class="synPreProc">from</span> scipy.spatial.distance <span class="synPreProc">import</span> pdist <span class="synPreProc">from</span> scipy.cluster.hierarchy <span class="synPreProc">import</span> linkage, fcluster <span class="synStatement">def</span> <span class="synIdentifier">clustering_fcluster</span>(): X, y = gen_data() S = pdist(X) Z = linkage(S, method=<span class="synConstant">&quot;average&quot;</span>) result = fcluster(Z, t=<span class="synConstant">4</span>, criterion=<span class="synConstant">&quot;maxclust&quot;</span>) d = defaultdict(<span class="synIdentifier">list</span>) <span class="synStatement">for</span> i, r <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(result): d[r].append(i) <span class="synStatement">for</span> k, v <span class="synStatement">in</span> d.items(): <span class="synIdentifier">print</span>(k, v) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: clustering_fcluster() </pre><pre class="code" data-lang="" data-unlink>1 [1, 2, 3, 5, 8, 9] なんとなく形が似てるかな・・・? 2 [7] # 独特の位置 3 [4, 6] # 似ているかと言うと微妙なものがある 4 [0] # 独特の位置</pre><p> いまいち納得感の少ない結果になりました。</p> <div class="section"> <h4 id="デンドログラムを描く">デンドログラムを描く</h4> <p> デンドログラムを描いてみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> scipy.spatial.distance <span class="synPreProc">import</span> pdist <span class="synPreProc">from</span> scipy.cluster.hierarchy <span class="synPreProc">import</span> linkage, dendrogram <span class="synStatement">def</span> <span class="synIdentifier">clustering_dendrogram</span>(): X, y = gen_data() S = pdist(X) Z = linkage(S, method=<span class="synConstant">&quot;average&quot;</span>) dendrogram(Z) plt.savefig(<span class="synConstant">&quot;dendro1.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: clustering_dendrogram() </pre><p><figure class="figure-image figure-image-fotolife" title="dendro1.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190211/20190211032134.png" alt="dendro1.png" title="f:id:hayataka2049:20190211032134p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>dendro1.png</figcaption></figure></p><p> 上の方でまとまっているのはあまりあてにならないので、実質的に意味があるのはとりあえず1,8と3,9あたりです。</p><p> 1,8は縦の真ん中あたりに集中するクラスタ。3,9は9の上の丸の左側を切り取ればほぼ3みたいな形になります。</p><p> 上のfclusterでやったのと同じ4つのクラスタに分かれるようにする方法がないのか? 基本的に違う考え方に基づいて着色されるので厳しいものがありますが、縦軸を見ながら4つに分かれるあたりを狙ってcolor_thresholdを決め打ちすると一応それに近いことはできます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> scipy.spatial.distance <span class="synPreProc">import</span> pdist <span class="synPreProc">from</span> scipy.cluster.hierarchy <span class="synPreProc">import</span> linkage, dendrogram <span class="synStatement">def</span> <span class="synIdentifier">clustering_dendrogram2</span>(): X, y = gen_data() S = pdist(X) Z = linkage(S, method=<span class="synConstant">&quot;average&quot;</span>) dendrogram(Z, color_threshold=<span class="synConstant">31</span>) plt.savefig(<span class="synConstant">&quot;dendro2.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: clustering_dendrogram2() </pre><p><figure class="figure-image figure-image-fotolife" title="dendro2.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190211/20190211033838.png" alt="dendro2.png" title="f:id:hayataka2049:20190211033838p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>dendro2.png</figcaption></figure></p><p> このcolor_thresholdは距離の近さがスレッショルド未満のものを1つにまとめる、という挙動です。スレッショルドより大きい距離はabove_threshold_color(デフォルトは"b"で青)になります。今回はスレッショルドより上で1サンプルで別れてそのまま1クラスタを形成するという厄介な子がいるので微妙な結果になってしまいますが、もう少し性質の良いデータだとうまく合わせることはできると思います。</p> </div> <div class="section"> <h4 id="遊ぶ">遊ぶ</h4> <p> せっかくmethodが7種類あるので、それぞれでやってみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> scipy.spatial.distance <span class="synPreProc">import</span> pdist <span class="synPreProc">from</span> scipy.cluster.hierarchy <span class="synPreProc">import</span> linkage, cophenet, dendrogram <span class="synStatement">def</span> <span class="synIdentifier">clustering_dendro_each_method</span>(): X, y = gen_data() methods = [<span class="synConstant">&quot;single&quot;</span>, <span class="synConstant">&quot;complete&quot;</span>, <span class="synConstant">&quot;average&quot;</span>, <span class="synConstant">&quot;weighted&quot;</span>, <span class="synConstant">&quot;centroid&quot;</span>, <span class="synConstant">&quot;median&quot;</span>, <span class="synConstant">&quot;ward&quot;</span>] fig, axes = plt.subplots(nrows=<span class="synConstant">2</span>, ncols=<span class="synConstant">4</span>, figsize=(<span class="synConstant">10</span>, <span class="synConstant">7</span>)) <span class="synStatement">for</span> ax, method <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(axes.ravel(), methods): S = pdist(X) Z = linkage(S, method=method) c, d = cophenet(Z, S) dendrogram(Z, color_threshold=<span class="synConstant">31</span>, ax=ax) ax.set_title(<span class="synConstant">&quot;{0} {1:.3f}&quot;</span>.format(method, c)) plt.savefig(<span class="synConstant">&quot;dendro_each.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: clustering_dendro_each_method() </pre><p><figure class="figure-image figure-image-fotolife" title="結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190211/20190211035720.png" alt="&#x7D50;&#x679C;" title="f:id:hayataka2049:20190211035720p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>結果</figcaption></figure></p><p> けっこう結果が変わるけど、細部はそれなりに揃っている感じ。コメントはとりあえず差し控えたいと思います。</p> </div> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> このように使えます。便利関数や引数が多いのがややこしいだけで、基本的な使い方そのものは難しくないです。</p><p> 階層型クラスタリングは結果の良し悪しを客観的に評価するのが難しいので、割り切って使うと良いと思います。デンドログラムの細かい枝分かれの順序どうこうより、各クラスタの成因を判別分析や検定などでちゃんと調べてあげる方が本当は大切です。</p> </div> Mon, 11 Feb 2019 03:59:43 +0900 hatenablog://entry/98012380864167334 python scipy クラスタリング 統計 機械学習 可視化 scipy.interpolate.griddataの内挿方法による違いを比較 https://www.haya-programming.com/entry/2018/12/14/100402 <div class="section"> <h3>はじめに</h3> <p> 以前、3次元のサンプルデータを内挿してmatplotlibでうまくプロットする方法について記事にしました。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F12%2F11%2F031322" title="xyzの点データを内挿してmeshgridにしmatplotlibでプロットする - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2018/12/11/031322">xyz&#x306E;&#x70B9;&#x30C7;&#x30FC;&#x30BF;&#x3092;&#x5185;&#x633F;&#x3057;&#x3066;meshgrid&#x306B;&#x3057;matplotlib&#x3067;&#x30D7;&#x30ED;&#x30C3;&#x30C8;&#x3059;&#x308B; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> この記事では内挿のアルゴリズムをデフォルトのlinearにして使いましたが、他の方法ではどうなるのか気になったので実験してみました。</p> </div> <div class="section"> <h3>使えるアルゴリズム</h3> <p> 選択肢は3つだけです。</p> <blockquote> <p>method : {‘linear’, ‘nearest’, ‘cubic’}, optional<br /> Method of interpolation. One of</p><p>nearest<br /> return the value at the data point closest to the point of interpolation. See NearestNDInterpolator for more details.</p><p>linear<br /> tessellate the input point set to n-dimensional simplices, and interpolate linearly on each simplex. See LinearNDInterpolator for more details.</p><p>cubic (1-D)<br /> return the value determined from a cubic spline.</p><p>cubic (2-D)<br /> return the value determined from a piecewise cubic, continuously differentiable (C1), and approximately curvature-minimizing polynomial surface. See CloughTocher2DInterpolator for more details.</p><p><a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html">scipy.interpolate.griddata &mdash; SciPy v1.3.0 Reference Guide</a></p> </blockquote> <p> なんとなくcubicには1-Dと2-Dの2つがあって「1次キュービック補間と2次キュービック補間? そんなのあったっけ」と思いがちですが、データが1次元か2次元かで使い分けられるだけで、ユーザが指定できるのは{‘linear’, ‘nearest’, ‘cubic’}のいずれかです。</p><p> それぞれ</p> <ul> <li>線形補間</li> <li>最近傍補間</li> <li>キュービック補間</li> </ul><p> です。詳しい中身は知らなくても、いずれも名前くらいは聞いたことがあると思います。</p><p><span style="font-size: 80%">スポンサーリンク</span><br /> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p> <p><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-6261827798827777" data-ad-slot="1744230936" data-ad-format="auto" data-full-width-responsive="true"></ins><br /> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script></p><br /> <p></p> </div> <div class="section"> <h3>実験</h3> <p> 二次元正規分布でサンプル数=128,512とし、それぞれの補間アルゴリズムで内挿します。結果をプロットして確認します。</p><p> また、回帰とみなしてRMSEを出してみました。</p><p> コードを以下に示します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> scipy <span class="synPreProc">import</span> stats <span class="synPreProc">from</span> scipy <span class="synPreProc">import</span> interpolate <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> mean_squared_error <span class="synStatement">def</span> <span class="synIdentifier">rmse</span>(true, pred): <span class="synStatement">return</span> mean_squared_error(true.ravel(), pred.ravel())**(<span class="synConstant">1</span>/<span class="synConstant">2</span>) <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): norm = stats.multivariate_normal(mean=[<span class="synConstant">2.0</span>, <span class="synConstant">3.0</span>], cov=[[<span class="synConstant">4</span>, <span class="synConstant">2</span>],[<span class="synConstant">2</span>,<span class="synConstant">4</span>]]) <span class="synComment"># samples</span> xy128 = np.random.uniform(low=-<span class="synConstant">10</span>, high=<span class="synConstant">10</span>, size=(<span class="synConstant">128</span>, <span class="synConstant">2</span>)) z128 = norm.pdf(xy128) xy512 = np.random.uniform(low=-<span class="synConstant">10</span>, high=<span class="synConstant">10</span>, size=(<span class="synConstant">512</span>, <span class="synConstant">2</span>)) z512 = norm.pdf(xy512) <span class="synComment"># xy meshgrid</span> x = y = np.linspace(-<span class="synConstant">10</span>, <span class="synConstant">10</span>, <span class="synConstant">500</span>) X, Y = np.meshgrid(x, y) Z = norm.pdf(np.vstack([X.ravel(), Y.ravel()]).T).reshape(X.shape) <span class="synComment"># plot</span> fig, axes = plt.subplots(nrows=<span class="synConstant">2</span>, ncols=<span class="synConstant">5</span>, figsize=(<span class="synConstant">10</span>,<span class="synConstant">5</span>)) plt.subplots_adjust(hspace=<span class="synConstant">0.6</span>, wspace=<span class="synConstant">0.4</span>) axes[<span class="synConstant">0</span>,<span class="synConstant">0</span>].pcolormesh(X, Y, Z, cmap=<span class="synConstant">&quot;jet&quot;</span>) axes[<span class="synConstant">0</span>,<span class="synConstant">0</span>].set_title(<span class="synConstant">&quot;true data&quot;</span>) axes[<span class="synConstant">1</span>,<span class="synConstant">0</span>].pcolormesh(X, Y, Z, cmap=<span class="synConstant">&quot;jet&quot;</span>) axes[<span class="synConstant">1</span>,<span class="synConstant">0</span>].set_title(<span class="synConstant">&quot;true data&quot;</span>) <span class="synStatement">for</span> i, (n_samples, xy, z) <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>( <span class="synIdentifier">zip</span>([<span class="synConstant">128</span>, <span class="synConstant">512</span>], [xy128, xy512], [z128, z512])): axes[i,<span class="synConstant">1</span>].scatter(xy[:,<span class="synConstant">0</span>], xy[:,<span class="synConstant">1</span>], c=z, cmap=<span class="synConstant">&quot;jet&quot;</span>) axes[i,<span class="synConstant">1</span>].set_title(<span class="synConstant">&quot;samples {}&quot;</span>.format(n_samples)) <span class="synStatement">for</span> j, i_method <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>([<span class="synConstant">&quot;nearest&quot;</span>, <span class="synConstant">&quot;linear&quot;</span>, <span class="synConstant">&quot;cubic&quot;</span>]): i_Z = interpolate.griddata(xy, z, (X, Y), method=i_method, fill_value=<span class="synConstant">0.0</span>) axes[i,j+<span class="synConstant">2</span>].pcolormesh(X, Y, i_Z, cmap=<span class="synConstant">&quot;jet&quot;</span>) axes[i,j+<span class="synConstant">2</span>].set_title(<span class="synConstant">&quot;{} {}</span><span class="synSpecial">\n</span><span class="synConstant">rmse={:.5f}&quot;</span>.format( i_method, <span class="synIdentifier">str</span>(n_samples), rmse(Z, i_Z))) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> なお、RMSEを計算する都合上、fill_value=0.0としています。デフォルトはnanですが、それだと計算できないので……。一応実際にnanの状態でも確認し、nanになるのはグラフの端(このデータではほぼ0.0)の領域だけであることを確認して以上の判断をしました。</p> </div> <div class="section"> <h3>結果と考察</h3> <p> プロットされた結果を示します。</p><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20181214/20181214095733.png" alt="result.png" title="f:id:hayataka2049:20181214095733p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> 見ての通り、ダメダメな最近傍補間、まあまあな線形補間、群を抜いて良いキュービック補間という関係です。cubicにしておけば良いのでは?</p><p> ただ、今回は真値そのものが補間が効きやすいなめらかなデータですが、実データはもう少しノイズが乗ったりして暴れることがあると思います。キュービック補間はオーバーシュート・アンダーシュートがあるらしいので、そういう場合でも対応できるように保険としてデフォルトがlinearになっているのかもしれません。まあ、無難なのはそっちでしょう。</p><p> 実用的には、両方やってみて大丈夫そうな方を選ぶことになるでしょう。</p> </div> <div class="section"> <h3>まとめ</h3> <p> cubicがよかったです。</p> </div> Fri, 14 Dec 2018 10:04:02 +0900 hatenablog://entry/10257846132684669060 python scipy matplotlib 統計 回帰 機械学習 可視化 scipyで確率分布のサンプルと確率密度関数を生成する https://www.haya-programming.com/entry/2018/12/02/035608 <p> 乱数データと確率密度関数を一緒にplotしてみたかったので、メモ。</p> <div class="section"> <h3>概要</h3> <p> scipy.statsでは様々な統計用のユーティリティが提供されています。大抵の分布はあるし、パラメータも好きに設定できます。</p><p><a href="https://docs.scipy.org/doc/scipy-0.16.1/reference/stats.html">Statistical functions (scipy.stats) &mdash; SciPy v0.16.1 Reference Guide</a></p><p> numpyにも充実したrandomモジュールがありますが、こちらは分布に従うデータの生成や、データのサンプリングなどしかできません(と思います)。</p><p><a href="https://docs.scipy.org/doc/numpy/reference/routines.random.html">https://docs.scipy.org/doc/numpy/reference/routines.random.html</a></p><p> なんとなく「データの生成はnumpyでできるけど、確率密度関数だとscipy使わないと駄目なのかな?」と思いがちですが、「データの生成」も実はscipyでできるので、numpyを使う必要性はありません。やったね。</p> </div> <div class="section"> <h3>方法</h3> <p> この記事では正規分布でやってみます。</p><p><a href="https://docs.scipy.org/doc/scipy-0.16.1/reference/generated/scipy.stats.norm.html#scipy.stats.norm">scipy.stats.norm &mdash; SciPy v0.16.1 Reference Guide</a></p><p> scipy.stats.normというものを使うのですが、これは実はクラスに近い使い方ができます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> scipy <span class="synPreProc">import</span> stats &gt;&gt;&gt; stats.norm &lt;scipy.stats._continuous_distns.norm_gen <span class="synIdentifier">object</span> at <span class="synConstant">0x7f8d839ede10</span>&gt; &gt;&gt;&gt; <span class="synIdentifier">type</span>(stats.norm) &lt;<span class="synStatement">class</span> <span class="synConstant">'scipy.stats._continuous_distns.norm_gen'</span>&gt; </pre><p> scipy.stats._continuous_distns.norm_gen objectというのはわかりづらいですが、早い話がファクトリであり、callするとオブジェクトを返します。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; stats.norm() &lt;scipy.stats._distn_infrastructure.rv_frozen <span class="synIdentifier">object</span> at <span class="synConstant">0x7f8da586ef28</span>&gt; </pre><p> 素性はいまいちわかりませんが、rv_frozen objectという名前から想像できる通り、事実上「確率分布のインスタンス」のように使えます。</p><p> つまり、分布のパラメータを渡してインスタンス化し、インスタンスを使ってそのパラメータの分布に従うサンプルを生成したり、確率密度関数を計算したりできるのですね。これを知ったときは喜びました。</p><p> 使えるメソッドはクラスによって微妙に違いがあるようですが、</p> <ul> <li>rvs:Random Variates(確率変量)</li> <li>pdf:Probability density function(確率密度関数)</li> </ul><p> の2つが今回使いたい「分布に従うデータ」と「確率密度関数」を返すメソッドです。</p><p> なお、これらのメソッドはstats.normのようなクラス(?)から呼ぶことも、インスタンス化したオブジェクト(?)(rv_frozenのオブジェクトと呼ぶべきか……)から呼ぶこともできますが、同じ名前でも使い方が違うので注意が必要です。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> scipy <span class="synPreProc">import</span> stats &gt;&gt;&gt; <span class="synIdentifier">help</span>(stats.norm.rvs) Help on method rvs <span class="synStatement">in</span> module scipy.stats._distn_infrastructure: rvs(*args, **kwds) method of scipy.stats._continuous_distns.norm_gen instance Random variates of given <span class="synIdentifier">type</span>. Parameters ---------- arg1, arg2, arg3,... : array_like The shape parameter(s) <span class="synStatement">for</span> the distribution (see docstring of the instance <span class="synIdentifier">object</span> <span class="synStatement">for</span> more information). loc : array_like, optional Location parameter (default=<span class="synConstant">0</span>). scale : array_like, optional Scale parameter (default=<span class="synConstant">1</span>). size : <span class="synIdentifier">int</span> <span class="synStatement">or</span> <span class="synIdentifier">tuple</span> of ints, optional Defining number of random variates (default <span class="synStatement">is</span> <span class="synConstant">1</span>). random_state : <span class="synIdentifier">None</span> <span class="synStatement">or</span> <span class="synIdentifier">int</span> <span class="synStatement">or</span> ``np.random.RandomState`` instance, optional If <span class="synIdentifier">int</span> <span class="synStatement">or</span> RandomState, use it <span class="synStatement">for</span> drawing the random variates. If <span class="synIdentifier">None</span>, rely on ``self.random_state``. Default <span class="synStatement">is</span> <span class="synIdentifier">None</span>. Returns ------- rvs : ndarray <span class="synStatement">or</span> scalar Random variates of given `size`. &gt;&gt;&gt; <span class="synIdentifier">help</span>(stats.norm().rvs) Help on method rvs <span class="synStatement">in</span> module scipy.stats._distn_infrastructure: rvs(size=<span class="synIdentifier">None</span>, random_state=<span class="synIdentifier">None</span>) method of scipy.stats._distn_infrastructure.rv_frozen instance </pre><p> やりたいことはわかるし、こういう仕様にしたのも理解できるけど、私のような初心者は戸惑う。scipyのドキュメントの簡潔さも相まって。</p> </div> <div class="section"> <h3>実験</h3> <p> こんなコードを動かしてみました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> scipy <span class="synPreProc">import</span> stats <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): normd = stats.norm(<span class="synConstant">100</span>, <span class="synConstant">7</span>) x = np.arange(<span class="synConstant">70</span>, <span class="synConstant">130</span>, <span class="synConstant">0.1</span>) pdf = normd.pdf(x) samples = normd.rvs(<span class="synConstant">1000</span>) fig, ax1 = plt.subplots() ax1.hist(samples, bins=<span class="synConstant">30</span>, color=<span class="synConstant">&quot;C0&quot;</span>) ax2 = ax1.twinx() ax2.plot(x, pdf, color=<span class="synConstant">&quot;C1&quot;</span>) plt.savefig(<span class="synConstant">&quot;result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 2軸グラフの描き方についてはこちらを参考にしました。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fsupersaiakujin%2Fitems%2Fe2ee4019adefce08e381" title="[python]matplotlibで左右に2つの軸があるグラフを書く方法 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://qiita.com/supersaiakujin/items/e2ee4019adefce08e381">[python]matplotlib&#x3067;&#x5DE6;&#x53F3;&#x306B;&#xFF12;&#x3064;&#x306E;&#x8EF8;&#x304C;&#x3042;&#x308B;&#x30B0;&#x30E9;&#x30D5;&#x3092;&#x66F8;&#x304F;&#x65B9;&#x6CD5; - Qiita</a></p><br /> <p> 結果は、以下のようになりました。</p><p><figure class="figure-image figure-image-fotolife" title="result.png"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20181202/20181202035323.png" alt="result.png" title="f:id:hayataka2049:20181202035323p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>result.png</figcaption></figure></p><p> ちゃんとできていますね。</p> </div> <div class="section"> <h3>まとめ</h3> <p> scipyでできることがわかった。scipyを信じていれば大抵のことはできる。</p> </div> Sun, 02 Dec 2018 03:56:08 +0900 hatenablog://entry/10257846132678802088 python Tips scipy numpy matplotlib 統計 【python】sklearnのPCAでloading(主成分負荷量)を計算する https://www.haya-programming.com/entry/2018/03/31/012428 <p> PCA(主成分分析)のloading<a href="#f-2ca0a7c0" name="fn-2ca0a7c0" title="主成分負荷量、あるいは因子負荷量とも(なぜか)言われますが、この記事ではloadingで通します。けっきょくヘタに和訳しないのがいちばんわかりやすい">*1</a>がほしいときがあります。</p><p> sklearnでは一発では出ません。</p><p> ドキュメントはここ。<br /> <a href="http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html">sklearn.decomposition.PCA &mdash; scikit-learn 0.21.2 documentation</a></p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#PCAcomponents_は確かにあるけど">PCA.components_は確かにあるけど・・・</a></li> <li><a href="#loadingを計算しよう">loadingを計算しよう</a></li> <li><a href="#罠だった">罠だった</a></li> <li><a href="#共分散行列のときはどうするのか">共分散行列のときはどうするのか</a></li> <li><a href="#loadingを使うと何が良いのか">loadingを使うと何が良いのか</a></li> </ul> <div class="section"> <h3 id="PCAcomponents_は確かにあるけど">PCA.components_は確かにあるけど・・・</h3> <p> 結論から先に言うと、PCA.components_はノルム1の固有ベクトルです。ノルムを測ってみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris &gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA &gt;&gt;&gt; iris = load_iris() &gt;&gt;&gt; pca = PCA(n_components=<span class="synConstant">2</span>) &gt;&gt;&gt; pca.fit(iris.data) PCA(copy=<span class="synIdentifier">True</span>, iterated_power=<span class="synConstant">'auto'</span>, n_components=<span class="synConstant">2</span>, random_state=<span class="synIdentifier">None</span>, svd_solver=<span class="synConstant">'auto'</span>, tol=<span class="synConstant">0.0</span>, whiten=<span class="synIdentifier">False</span>) &gt;&gt;&gt; pca.components_ array([[ <span class="synConstant">0.36158968</span>, -<span class="synConstant">0.08226889</span>, <span class="synConstant">0.85657211</span>, <span class="synConstant">0.35884393</span>], [ <span class="synConstant">0.65653988</span>, <span class="synConstant">0.72971237</span>, -<span class="synConstant">0.1757674</span> , -<span class="synConstant">0.07470647</span>]]) &gt;&gt;&gt; np.linalg.norm(pca.components_, axis=<span class="synConstant">1</span>) array([<span class="synConstant">1.</span>, <span class="synConstant">1.</span>]) </pre><p> まあ、loadingも固有ベクトルには違いないのですが、ノルムを整えてやる必要があります。</p> </div> <div class="section"> <h3 id="loadingを計算しよう">loadingを計算しよう</h3> <p> 教科書などによく書いてあることですが、第<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20i" alt=" i"/>主成分に対応する元の変数のloadingは次の式で出せます。<br /> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20loading%20%3D%20%5Csqrt%28%5Clambda_i%29%20eigenvector" alt=" loading = \sqrt(\lambda_i) eigenvector"/><br />  <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Clambda_i" alt=" \lambda_i"/>は固有値。 eigenvectorは固有ベクトルで、元の変数の数だけ次元がありますから、これで良いわけです(雑な説明ですが・・・)。</p><p> ということで、pythonで同様にやってみましょう。固有値はexplained_varianceに入っています。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; pca.components_*np.c_[np.sqrt(pca.explained_variance_)] <span class="synComment"># 縦ベクトルに変換する必要がある</span> array([[ <span class="synConstant">0.74322652</span>, -<span class="synConstant">0.16909891</span>, <span class="synConstant">1.76063406</span>, <span class="synConstant">0.73758279</span>], [ <span class="synConstant">0.32313741</span>, <span class="synConstant">0.35915163</span>, -<span class="synConstant">0.08650963</span>, -<span class="synConstant">0.03676921</span>]]) </pre><p> できました。これがloadingです。・・・と思ったけど、1を超えちゃってますね。なんてこった。</p> </div> <div class="section"> <h3 id="罠だった">罠だった</h3> <p> 固有値は分散なので、データのスケールに依存します。</p><p> とりあえず入力をスケーリングしてみよう。上の式は相関行列から行くときのものでした。なのでこれで平気なはず。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler <span class="synStatement">as</span> SS &gt;&gt;&gt; ss = SS() &gt;&gt;&gt; data = ss.fit_transform(iris.data) &gt;&gt;&gt; pca.fit(data) PCA(copy=<span class="synIdentifier">True</span>, iterated_power=<span class="synConstant">'auto'</span>, n_components=<span class="synConstant">2</span>, random_state=<span class="synIdentifier">None</span>, svd_solver=<span class="synConstant">'auto'</span>, tol=<span class="synConstant">0.0</span>, whiten=<span class="synIdentifier">False</span>) &gt;&gt;&gt; pca.components_*np.c_[np.sqrt(pca.explained_variance_)] array([[ <span class="synConstant">0.89421016</span>, -<span class="synConstant">0.45081822</span>, <span class="synConstant">0.99500666</span>, <span class="synConstant">0.96822861</span>], [ <span class="synConstant">0.35854928</span>, <span class="synConstant">0.89132754</span>, <span class="synConstant">0.02031465</span>, <span class="synConstant">0.06299656</span>]]) </pre><p> 1を超えなくなくてめでたし、ということよりも、数字が変わったことの方が問題で、これで本当に正しいのかという疑念が生じてきました。</p><p> 確認のために元の特徴と主成分の相関係数を直接測ってみます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; X = pca.fit_transform(data) &gt;&gt;&gt; np.corrcoef(np.hstack([iris.data, X]), rowvar=<span class="synIdentifier">False</span>) array([[ <span class="synConstant">1.00000000e+00</span>, -<span class="synConstant">1.09369250e-01</span>, <span class="synConstant">8.71754157e-01</span>, <span class="synConstant">8.17953633e-01</span>, <span class="synConstant">8.91224479e-01</span>, <span class="synConstant">3.57352114e-01</span>], [-<span class="synConstant">1.09369250e-01</span>, <span class="synConstant">1.00000000e+00</span>, -<span class="synConstant">4.20516096e-01</span>, -<span class="synConstant">3.56544090e-01</span>, -<span class="synConstant">4.49312976e-01</span>, <span class="synConstant">8.88351481e-01</span>], [ <span class="synConstant">8.71754157e-01</span>, -<span class="synConstant">4.20516096e-01</span>, <span class="synConstant">1.00000000e+00</span>, <span class="synConstant">9.62757097e-01</span>, <span class="synConstant">9.91684422e-01</span>, <span class="synConstant">2.02468206e-02</span>], [ <span class="synConstant">8.17953633e-01</span>, -<span class="synConstant">3.56544090e-01</span>, <span class="synConstant">9.62757097e-01</span>, <span class="synConstant">1.00000000e+00</span>, <span class="synConstant">9.64995787e-01</span>, <span class="synConstant">6.27862218e-02</span>], [ <span class="synConstant">8.91224479e-01</span>, -<span class="synConstant">4.49312976e-01</span>, <span class="synConstant">9.91684422e-01</span>, <span class="synConstant">9.64995787e-01</span>, <span class="synConstant">1.00000000e+00</span>, <span class="synConstant">2.08904471e-17</span>], [ <span class="synConstant">3.57352114e-01</span>, <span class="synConstant">8.88351481e-01</span>, <span class="synConstant">2.02468206e-02</span>, <span class="synConstant">6.27862218e-02</span>, <span class="synConstant">2.08904471e-17</span>, <span class="synConstant">1.00000000e+00</span>]]) </pre><p>  下の二行の4列目までを見てください。微妙に誤差があるような気はしますが(小数点以下3桁でずれてきてるので微妙ってほど微妙でもないが)、たぶん同じ数字になっています。</p><p> 微妙な誤差については、丸め誤差などが累積した、実は計算間違ってる、といった理由が考えられます。前者ならまだ許せるけど、後者はやだな・・・。</p> </div> <div class="section"> <h3 id="共分散行列のときはどうするのか">共分散行列のときはどうするのか</h3> <p> どうするんだろうね・・・。</p><p> 2019/06/22追記<br />  手順は増えますが、スケールを考慮すれば同様に行えるようです。</p> <blockquote> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20190622/20190622141121.png" alt="&#x8CA0;&#x8377;&#x91CF;" title="f:id:hayataka2049:20190622141121p:plain" class="hatena-fotolife" itemprop="image"></span></p><p>出典:<a href="http://manabukano.brilliant-future.net/document/text-PCA.pdf">http://manabukano.brilliant-future.net/document/text-PCA.pdf</a> p.10</p> </blockquote> </div> <div class="section"> <h3 id="loadingを使うと何が良いのか">loadingを使うと何が良いのか</h3> <p> 相関係数なので、「どれくらい効いてるか」がよくわかります。よくある0.3以下なら~とか0.7以上なら~という論法ができます。それだけといえばそれだけ。</p><p> このように取扱が大変なので、固有ベクトルのまま解釈した方が楽かもという気がしてきました。各主成分の寄与率はexplained_variance_ratio_で得られる訳だし、寄与率の大きい軸の固有ベクトルの大きい次元を見ればどんな感じかはわかるし・・・。</p><p> でもまあ、一応(入力をスケーリングすれば)大体出るということはわかったので、これでよしとします。</p><p> 共分散行列でやるときのやり方は、どなたか詳しい方に教えて頂けると助かります。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-2ca0a7c0" name="f-2ca0a7c0" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">主成分負荷量、あるいは因子負荷量とも(なぜか)言われますが、この記事ではloadingで通します。けっきょくヘタに和訳しないのがいちばんわかりやすい</span></p> </div> Sat, 31 Mar 2018 01:24:28 +0900 hatenablog://entry/17391345971630888436 python sklearn 統計 主成分分析 機械学習 【python】sklearnで因子分析を試す https://www.haya-programming.com/entry/2018/03/31/002211 <p> pythonで因子分析をやる人はあまりいないようだが、sklearnにはしっかりモデルが存在している。ついさっき気づいた。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.FactorAnalysis.html">sklearn.decomposition.FactorAnalysis &mdash; scikit-learn 0.20.1 documentation</a></p><p> 因子分析自体は前からどんなものなのか興味があり、かといってググるとRだったりSPSSだったりばっかり出てきて辟易していたのだが、sklearnにあると都合が良い。さっそく使ってみよう。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#とりあえずirisをプロットする">とりあえずirisをプロットする</a></li> <li><a href="#とりあえずcomponentsを見る">とりあえずcomponentsを見る</a></li> <li><a href="#使えることはわかった">使えることはわかった</a></li> </ul> <div class="section"> <h3 id="とりあえずirisをプロットする">とりあえずirisをプロットする</h3> <p> 私だけでも何十回もやってきた、世界中では何万回とやられてきたirisの二次元可視化をやってみる。</p><p> 次のようなコードを書いた。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span> <span class="synPreProc">from</span> copy <span class="synPreProc">import</span> deepcopy <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA, FactorAnalysis <span class="synStatement">as</span> FA <span class="synPreProc">from</span> sklearn.pipeline <span class="synPreProc">import</span> Pipeline <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synStatement">def</span> <span class="synIdentifier">decomp_and_plot</span>(dataset, model, file_name): X = model.fit_transform(dataset.data) plt.figure() plt.scatter(X[:,<span class="synConstant">0</span>], X[:,<span class="synConstant">1</span>], c=dataset.target/<span class="synIdentifier">len</span>(dataset.target_names)) plt.savefig(file_name) <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): iris = load_iris() ss = StandardScaler() pca = PCA(n_components=<span class="synConstant">2</span>) pl = Pipeline([(<span class="synConstant">&quot;scaler&quot;</span>, ss), (<span class="synConstant">&quot;pca&quot;</span>, deepcopy(pca))]) fa = FA(n_components=<span class="synConstant">2</span>, max_iter=<span class="synConstant">5000</span>) decomp_and_plot(iris, pca, <span class="synConstant">&quot;pca_plt.png&quot;</span>) decomp_and_plot(iris, pl, <span class="synConstant">&quot;spca_plt.png&quot;</span>) decomp_and_plot(iris, fa, <span class="synConstant">&quot;fa_plt.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> PCA、変数をスケーリングしたPCA(相関行列を使うことと等価)、因子分析でそれぞれplotしてみる。</p><p> 結果はこれ。</p><p><figure class="figure-image figure-image-fotolife" title="PCAの結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180330/20180330232127.png" alt="f:id:hayataka2049:20180330232127p:plain" title="f:id:hayataka2049:20180330232127p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>PCAの結果</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="PCA(相関行列)の結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180330/20180330232132.png" alt="f:id:hayataka2049:20180330232132p:plain" title="f:id:hayataka2049:20180330232132p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>PCA(相関行列)の結果</figcaption></figure> 相関行列はぱっと見いまいち(この絵一枚でダメかどうかは判断できないが)。</p><p><figure class="figure-image figure-image-fotolife" title="因子分析の結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180330/20180330232134.png" alt="f:id:hayataka2049:20180330232134p:plain" title="f:id:hayataka2049:20180330232134p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>因子分析の結果</figcaption></figure> うーん、相関行列のとも違うし、なんとも言い難いというか、素人目にはぶっちゃけあんまり良くないように見えるのだが、確率モデルなのでノイズの存在を仮定して見るとこうなるということだろう。</p> </div> <div class="section"> <h3 id="とりあえずcomponentsを見る">とりあえずcomponentsを見る</h3> <p> 次のようなmain2を作り、実行した。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">main2</span>(): iris = load_iris() <span class="synIdentifier">print</span>(iris.feature_names) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;pca&quot;</span>) pca = PCA(n_components=<span class="synConstant">2</span>) pca.fit(iris.data) <span class="synIdentifier">print</span>(pca.components_) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;fa&quot;</span>) fa = FA(n_components=<span class="synConstant">2</span>, max_iter=<span class="synConstant">5000</span>) fa.fit(iris.data) <span class="synIdentifier">print</span>(fa.components_) </pre><p> 結果</p> <pre class="code lang-python" data-lang="python" data-unlink>[<span class="synConstant">'sepal length (cm)'</span>, <span class="synConstant">'sepal width (cm)'</span>, <span class="synConstant">'petal length (cm)'</span>, <span class="synConstant">'petal width (cm)'</span>] pca [[ <span class="synConstant">0.36158968</span> -<span class="synConstant">0.08226889</span> <span class="synConstant">0.85657211</span> <span class="synConstant">0.35884393</span>] [ <span class="synConstant">0.65653988</span> <span class="synConstant">0.72971237</span> -<span class="synConstant">0.1757674</span> -<span class="synConstant">0.07470647</span>]] fa [[ <span class="synConstant">0.72577591</span> -<span class="synConstant">0.17754023</span> <span class="synConstant">1.75733754</span> <span class="synConstant">0.73196365</span>] [-<span class="synConstant">0.37036948</span> -<span class="synConstant">0.24060118</span> <span class="synConstant">0.02793388</span> <span class="synConstant">0.04121372</span>]] </pre><p> プロット結果から予想される通り、両者のcomponentsはよく似通っている。</p><p> これがloadingなのかどうかはぶっちゃけよくわからないのだが(というか1を超えてくる時点でたぶん違うのだろうが)、とりあえずloadingだと思って解釈する。</p><p> 第一因子は花弁の長さと幅、がく片の長さに対応しているので花の大きさに対応しているっぽい。花の大きさとがく片の幅はなぜか若干反比例する。</p><p> 第二因子は花弁に関する係数が小さいので、がく片の大きさを表す因子と言って良さそうである。</p><p> こんなところか。</p> </div> <div class="section"> <h3 id="使えることはわかった">使えることはわかった</h3> <p> だから何? って言われると、正直答えに窮しますが・・・とにかく使えます。主成分分析で良いじゃんと言われたら何も言い返せません。<br />  </p> </div> Sat, 31 Mar 2018 00:22:11 +0900 hatenablog://entry/17391345971630875476 python sklearn 統計 次元削減 Pipeline 主成分分析 機械学習 可視化 【python】pythonで主成分分析のバイプロット https://www.haya-programming.com/entry/2018/03/28/231305 <p> バイプロット(Biplot)という主成分分析(PCA)の結果の可視化方法があります。</p><p> すごく大雑把に言うと、PCAによる写像の前の空間の各特徴(軸)が写像先の空間のどこに向いているかを可視化する方法です。</p><p> 具体的には、主成分ベクトル(因子負荷量などを使う場合もあります)と散布図を同じ図にplotします。これらを組み合わせることで、元の空間の性質が二次元(もしかしたら3次元)で手に取るようにわかります<a href="#f-d53b0a21" name="fn-d53b0a21" title="本当に手に取るようにわかるかはデータと見る人に依存しますが・・・">*1</a>。</p><p> バイプロットはR言語だと簡単に描けるらしいのですが、我らがpythonには(少なくとも一般的なライブラリには)そんな便利なものはありません。ちょっと困るのですが、幸い英語圏にはちらほらやりかたの情報があります。しかし、それはそれでページごとにやってることが違ったりして、(申し訳ないのですが)微妙に信用できなかったりします。</p><p> で、けっきょく自分で書いてみることにしました。なお、参考にしたのはこの辺です。</p> <ul> <li><a href="https://github.com/teddyroland/python-biplot">GitHub - teddyroland/python-biplot: Generates simple biplot using common scientific Python packages</a></li> <li><a href="https://sukhbinder.wordpress.com/2015/08/05/biplot-with-python/">Biplot with Python | SukhbinderSingh.com</a></li> <li><a href="http://okomestudio.net/biboroku/?p=2292">http://okomestudio.net/biboroku/?p=2292</a></li> </ul> <div class="section"> <h3>方針</h3> <p> まずsklearnの公式ドキュメントをできるだけ良く読み込みます。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html">sklearn.decomposition.PCA &mdash; scikit-learn 0.22.1 documentation</a></p><p> PCA.components_が固有ベクトルであり、データをセンタリングしてこれと掛けるとPCAの出力が出てくることは<a href="https://www.haya-programming.com/entry/2018/03/28/222101">&#x524D;&#x56DE;&#x306E;&#x8A18;&#x4E8B;</a>で確認しました。</p><p> 固有ベクトル行列が主成分*元のデータの特徴という形になっているとして、横に見ると負荷量(みたいなもの。本当は対応する固有値のsqrtを掛け算してやらないといけない)に、縦に見ると元の写像先で表現された特徴の軸になります。</p><p> つまり、その軸をプロットするだけです。</p><p> なお、この辺は微妙に議論があるようです。私もこれがどこまで正しい方法なのかは自信が持てません。</p><p> 参考:<br /> <a href="https://cis-jp.blogspot.jp/2012/09/blog-post_9.html">&#x8272;&#x3005;&#x3068;&#x8003;&#x3048;&#x3066;&#x307F;&#x308B;: &#x6587;&#x7CFB;&#x306E;&#x305F;&#x3081;&#x306E;&#x300C;&#x4E3B;&#x6210;&#x5206;&#x5206;&#x6790;&#x306E;&#x53EF;&#x8996;&#x5316;&#x300D;&#xFF08;&#xFF12;&#xFF09;</a></p><p> だけど今回は、データをセンタリングしてPCAを学習させた上で、各軸に対応するone-hot vectorを渡してtransformしたら確かに上に書いた方法通りで上手く行きました(biplotの線の上に載った)。なので、「これで良いんだろう」と勝手に判断しました。どこまで妥当かはよくわからないんですけど。</p> </div> <div class="section"> <h3>実装</h3> <p> こんな感じで書きました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span> <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris, load_wine <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> StandardScaler <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synStatement">def</span> <span class="synIdentifier">biplot</span>(dataset, scale=<span class="synIdentifier">False</span>, arrow_mul=<span class="synConstant">1</span>, text_mul=<span class="synConstant">1.1</span>): <span class="synStatement">if</span> scale: ss = StandardScaler() X = ss.fit_transform(dataset.data) <span class="synStatement">else</span>: X = dataset.data <span class="synStatement">if</span> <span class="synIdentifier">hasattr</span>(dataset, <span class="synConstant">&quot;feature_names&quot;</span>): feature_names = <span class="synIdentifier">list</span>(dataset.feature_names) <span class="synStatement">else</span>: feature_names = [<span class="synConstant">&quot;F{0}&quot;</span>.format(i) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(dataset.data.shape[<span class="synConstant">1</span>])] pca = PCA(n_components=<span class="synConstant">2</span>) X = pca.fit_transform(X) x_data = X[:,<span class="synConstant">0</span>] y_data = X[:,<span class="synConstant">1</span>] pc0 = pca.components_[<span class="synConstant">0</span>] pc1 = pca.components_[<span class="synConstant">1</span>] plt.figure() plt.scatter(x_data, y_data, c=dataset.target/<span class="synIdentifier">len</span>(<span class="synIdentifier">set</span>(dataset.target)), marker=<span class="synConstant">&quot;.&quot;</span>) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(pc0.shape[<span class="synConstant">0</span>]): plt.arrow(<span class="synConstant">0</span>, <span class="synConstant">0</span>, pc0[i]*arrow_mul, pc1[i]*arrow_mul, color=<span class="synConstant">'r'</span>) plt.text(pc0[i]*arrow_mul*text_mul, pc1[i]*arrow_mul*text_mul, feature_names[i], color=<span class="synConstant">'r'</span>) plt.show() <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): iris = load_iris() wine = load_wine() biplot(iris, arrow_mul=<span class="synConstant">2.5</span>, scale=<span class="synIdentifier">True</span>) biplot(wine, arrow_mul=<span class="synConstant">6</span>, scale=<span class="synIdentifier">True</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 今回はsklearnのデータセットを渡す形で関数にまとめました。ま、もしこのコードを流用したい人がいたら、必要なロジックだけ上手く切り出してください。</p><p> 結果は、こんな画像が出ます。</p><p><figure class="figure-image figure-image-fotolife" title="irisのバイプロット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328230804.png" alt="iris&#x306E;&#x30D0;&#x30A4;&#x30D7;&#x30ED;&#x30C3;&#x30C8;" title="f:id:hayataka2049:20180328230804p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>irisのバイプロット</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="wineのバイプロット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328230809.png" alt="wine&#x306E;&#x30D0;&#x30A4;&#x30D7;&#x30ED;&#x30C3;&#x30C8;" title="f:id:hayataka2049:20180328230809p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>wineのバイプロット</figcaption></figure></p><p> 上手く行ってる感じです。</p><p> なお、上のコードでは変数をスケーリングしています(相関行列でPCAするのと等価)。スケーリングしなくてもできますが、やった方が矢印の長さが揃いやすいです(逆に変数のスケールを重視してPCAしたいときは、スケーリングしてはいけない。ケースバイケース)。</p> </div> <div class="section"> <h3>まとめ</h3> <p> これくらい自作しなくても済めば良いのにと思いました。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-d53b0a21" name="f-d53b0a21" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">本当に手に取るようにわかるかはデータと見る人に依存しますが・・・</span></p> </div> Wed, 28 Mar 2018 23:13:05 +0900 hatenablog://entry/17391345971630318038 python 主成分分析 matplotlib 統計 次元削減 機械学習 可視化 【python】numpyで主成分分析を実装してみた https://www.haya-programming.com/entry/2018/03/28/222101 <p> numpyでPCA(principal component analysis:主成分分析)を実装してみました。自分の理解を深めるためです。</p><p> sklearnに実装されているものと同じ結果を出すことを目標にしました。最終的には上手く行きました。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a></li> <li><a href="#実装">実装</a></li> <li><a href="#結果">結果</a></li> <li><a href="#まとめ">まとめ</a></li> </ul> <div class="section"> <h3 id="概要">概要</h3> <p> 主成分分析のアルゴリズムの解説は他に譲ります。これは実装してみた記事です。</p><p> 実装のやり方は色々あるようですが、一番基本的な(だと思う)共分散行列の固有値と固有ベクトルを求める方法で行きます。</p><p> やるべきこととしては、</p> <ol> <li>データをセンタリングする(列ごとに平均を引く)</li> <li>共分散行列を計算する</li> <li>固有値と固有ベクトルを計算</li> <li>データを固有ベクトルを使って写像する</li> </ol><p> これらを実装すれば行けるはずです。というか、これで行くことにしました。</p> </div> <div class="section"> <h3 id="実装">実装</h3> <p> 書いたソースコードを以下に示します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synStatement">class</span> <span class="synIdentifier">MyPCA</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, n_components=<span class="synConstant">2</span>): self.n_components = n_components <span class="synStatement">def</span> <span class="synIdentifier">fit_transform</span>(self, X): <span class="synConstant">&quot;&quot;&quot;横着してfit_transformしか実装してない</span> <span class="synConstant"> &quot;&quot;&quot;</span> <span class="synComment"># 平均を0にする</span> X = X - X.mean(axis=<span class="synConstant">0</span>) <span class="synComment"># 共分散行列を作る</span> self.cov_ = np.cov(X, rowvar=<span class="synIdentifier">False</span>) <span class="synComment"># 固有値と固有ベクトルを求めて固有値の大きい順にソート</span> l, v = np.linalg.eig(self.cov_) l_index = np.argsort(l)[::-<span class="synConstant">1</span>] self.l_ = l[l_index] self.v_ = v[:,l_index] <span class="synComment"># 列ベクトルなのに注意</span> <span class="synComment"># components_(固有ベクトル行列を途中まで取り出す)を作る</span> self.components_ = self.v_[:,:self.n_components].T <span class="synComment"># データとcomponents_をかける</span> <span class="synComment"># 上と下で二回転置してるのアホ・・・</span> T = (np.mat(X)*(np.mat(self.components_.T))).A <span class="synComment"># 出力</span> <span class="synStatement">return</span> T <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): iris = load_iris() pca = PCA(n_components=<span class="synConstant">2</span>) sklearn_X = pca.fit_transform(iris.data) my_pca = MyPCA() my_X = my_pca.fit_transform(iris.data) <span class="synIdentifier">print</span>(pca.explained_variance_) <span class="synIdentifier">print</span>(my_pca.l_) <span class="synIdentifier">print</span>(pca.components_) <span class="synIdentifier">print</span>(my_pca.components_) plt.figure() plt.scatter(sklearn_X[:,<span class="synConstant">0</span>], sklearn_X[:,<span class="synConstant">1</span>], c=iris.target/<span class="synConstant">3</span>) plt.savefig(<span class="synConstant">&quot;sklearn_resut.png&quot;</span>) plt.figure() plt.scatter(my_X[:,<span class="synConstant">0</span>], my_X[:,<span class="synConstant">1</span>]*-<span class="synConstant">1</span>, c=iris.target/<span class="synConstant">3</span>) plt.savefig(<span class="synConstant">&quot;my_result.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> numpyを使ったので簡単に書けました。アルゴリズム部分はコメントで解説を入れたので、それを読めばどんな感じかは理解して頂けると思います。</p> </div> <div class="section"> <h3 id="結果">結果</h3> <p> mainのテキスト出力を見ると、次のようになっていました。</p> <pre class="code" data-lang="" data-unlink># 固有値 [4.22484077 0.24224357] [4.22484077 0.24224357 0.07852391 0.02368303] # components_ [[ 0.36158968 -0.08226889 0.85657211 0.35884393] [ 0.65653988 0.72971237 -0.1757674 -0.07470647]] [[ 0.36158968 -0.08226889 0.85657211 0.35884393] [-0.65653988 -0.72971237 0.1757674 0.07470647]]</pre><p> 固有値が余計に出ちゃってますが、これは別に構いません。また、componentsの2次元目が符号反転していますが、これも特に問題ないこと(のはず)なので無視します。</p><p> 自作の方は第二主成分を反転させてプロットしてみました。</p><p><figure class="figure-image figure-image-fotolife" title="sklearnのPCAでirisを可視化"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328221612.png" alt="sklearn&#x306E;PCA&#x3067;iris&#x3092;&#x53EF;&#x8996;&#x5316;" title="f:id:hayataka2049:20180328221612p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>sklearnのPCAでirisを可視化</figcaption></figure></p><p><figure class="figure-image figure-image-fotolife" title="自作PCAでirisを可視化"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180328/20180328221633.png" alt="&#x81EA;&#x4F5C;PCA&#x3067;iris&#x3092;&#x53EF;&#x8996;&#x5316;" title="f:id:hayataka2049:20180328221633p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>自作PCAでirisを可視化</figcaption></figure></p><p> 同じ図を2つ載せるなって怒られそうですが・・・とにかく上手く行ったようです。</p> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> numpyで実装してみたら思ったより簡単だったので、これで当分は「わかった気」になれそうです。</p><p> ただ、今回は特異値分解やらなかったので、それはまた宿題ということで・・・。</p> </div> Wed, 28 Mar 2018 22:21:01 +0900 hatenablog://entry/17391345971630301357 python numpy 主成分分析 統計 sklearn 次元削減 機械学習 可視化 【python】scipy.statsのzscoreで警告が出るときの対策 https://www.haya-programming.com/entry/2018/03/20/173020 <div class="section"> <h3>概要</h3> <p> z得点を計算しようとしたとき、このような警告を見かけることがあります。</p> <pre class="code" data-lang="" data-unlink>RuntimeWarning: invalid value encountered in true_divide</pre><p> これが出た場合、結果にはnanが含まれています。なので後段の分析で落ちたりします。</p> <pre class="code" data-lang="" data-unlink>&gt;&gt;&gt; import numpy as np &gt;&gt;&gt; from scipy.stats import zscore &gt;&gt;&gt; a = np.array([[1,2,3,4], [1,2,3,4],[1,3,4,5]]) &gt;&gt;&gt; zscore(a, axis=0) stats.py:2248: RuntimeWarning: invalid value encountered in true_divide return (a - mns) / sstd array([[ nan, -0.70710678, -0.70710678, -0.70710678], [ nan, -0.70710678, -0.70710678, -0.70710678], [ nan, 1.41421356, 1.41421356, 1.41421356]])</pre><p> どうしてエラーになるかというと、z得点は標準偏差で割るので、標準偏差が0だと0除算エラーが発生するからです。標準偏差0の列が含まれるようなゴミだらけの汚い疎行列をそのまま入れると、これが出来ます。</p> </div> <div class="section"> <h3>対策</h3> <p> どうせ標準偏差0の軸とか要らないので、予め消し飛ばしておく。</p> <pre class="code" data-lang="" data-unlink>&gt;&gt;&gt; a[:,np.std(a, axis=0) != 0] array([[2, 3, 4], [2, 3, 4], [3, 4, 5]]) &gt;&gt;&gt; zscore(a[:,np.std(a, axis=0) != 0], axis=0) array([[-0.70710678, -0.70710678, -0.70710678], [-0.70710678, -0.70710678, -0.70710678], [ 1.41421356, 1.41421356, 1.41421356]])</pre><p> めでたしめでたし。</p> </div> Tue, 20 Mar 2018 17:30:20 +0900 hatenablog://entry/17391345971627566771 python scipy 統計 【python】混合ガウスモデル (GMM)でハード・ソフトクラスタリング https://www.haya-programming.com/entry/2018/03/06/043950 <div class="section"> <h3>はじめに</h3> <p> 先日はFuzzy c-meansによるソフトクラスタリングを行いました。</p><p><a href="http://hayataka2049.hatenablog.jp/entry/2018/03/03/202558">&#x3010;python&#x3011;skfuzzy&#x306E;Fuzzy c-means&#x3067;&#x30BD;&#x30D5;&#x30C8;&#x30AF;&#x30E9;&#x30B9;&#x30BF;&#x30EA;&#x30F3;&#x30B0; - &#x9759;&#x304B;&#x306A;&#x308B;&#x540D;&#x8F9E;</a></p><p> ソフトクラスタリングの有名な手法としてはFuzzy c-meansの他に、混合ガウスモデル(混合正規分布モデル)を使った手法があります。この手法はデータが「複数の正規分布から構成されている」と仮定し、その正規分布のパラメタ<a href="#f-c5112e5f" name="fn-c5112e5f" title="一次元なら平均と分散、多次元なら共分散みたいな話になってくるのだろうか?">*1</a>をEMアルゴリズム(expectation–maximization algorithm)という手法を使って最尤推定します。</p><p> ごちゃごちゃと書きましたが、要するに「3つのクラスタにクラスタリングしたければ、(各クラスタのデータの分布が正規分布に従うと仮定して)3つの正規分布が重なりあってると思ってGMMを使って解く」という乱暴なお話です。正規分布が重なりあっているとみなすということは、どの分布に属するかも確率でわかる訳で、これがソフトクラスタリングに使える理由です。ハードクラスタリングに使いたいときは、確率最大のクラスタラベルに振ることになるかと思います。</p><p> このGMM、pythonではsklearnに入っているので簡単に使えます。</p><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.mixture.GaussianMixture.html">sklearn.mixture.GaussianMixture &mdash; scikit-learn 0.20.1 documentation</a></p><p> ということで、他のクラスタリング手法と比較してみることにしました。</p><p><span style="font-size: 80%">スポンサーリンク</span><br /> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p> <p><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-6261827798827777" data-ad-slot="1744230936" data-ad-format="auto" data-full-width-responsive="true"></ins><br /> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script></p><br /> <p></p> </div> <div class="section"> <h3>実験の説明</h3> <p> 先日の記事でやったのと同様、irisをPCAで二次元に落としたデータに対してクラスタリングを行います。クラスタリング結果(所属するクラスタの確率)はirisが3クラスのデータなのを利用し、色(RGB)で表現します。</p><p> 比較するクラスタリング手法はk-means(ハード)、Fuzzy c-means(ソフト)、GMM(ハード・ソフト)です。</p><p> 前回はFuzzy c-meansのパラメタmを動かして結果を見たりしましたが、今回これは2で決め打ちにします。</p><p> 実験用ソースコードは次のものです。走らせるにはいつもの定番ライブラリ以外にscikit-fuzzyというライブラリを入れる必要があります(あるいはFuzzy c-means関連の部分をコメントアウトするか。でもskfuzzyはpipで一発で入るし、入れておいても別に損はない)。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> sklearn.datasets <span class="synPreProc">import</span> load_iris <span class="synPreProc">from</span> sklearn.decomposition <span class="synPreProc">import</span> PCA <span class="synPreProc">from</span> sklearn.cluster <span class="synPreProc">import</span> KMeans <span class="synStatement">as</span> KM <span class="synPreProc">from</span> sklearn.mixture <span class="synPreProc">import</span> GaussianMixture <span class="synStatement">as</span> GMM <span class="synPreProc">from</span> matplotlib <span class="synPreProc">import</span> pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> skfuzzy.cluster <span class="synPreProc">import</span> cmeans <span class="synStatement">def</span> <span class="synIdentifier">target_to_color</span>(target): <span class="synStatement">if</span> <span class="synIdentifier">type</span>(target) == np.ndarray: <span class="synStatement">return</span> (target[<span class="synConstant">0</span>], target[<span class="synConstant">1</span>], target[<span class="synConstant">2</span>]) <span class="synStatement">else</span>: <span class="synStatement">return</span> <span class="synConstant">&quot;rgb&quot;</span>[target] <span class="synStatement">def</span> <span class="synIdentifier">plot_data</span>(data, target, filename=<span class="synConstant">&quot;fig.png&quot;</span>): plt.figure() plt.scatter(data[:,<span class="synConstant">0</span>], data[:,<span class="synConstant">1</span>], c=[target_to_color(t) <span class="synStatement">for</span> t <span class="synStatement">in</span> target]) plt.savefig(filename) <span class="synStatement">def</span> <span class="synIdentifier">gen_data</span>(): iris = load_iris() pca = PCA(n_components=<span class="synConstant">2</span>) <span class="synStatement">return</span> pca.fit_transform(iris.data), iris.target <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): data, target = gen_data() plot_data(data, target, filename=<span class="synConstant">&quot;origin.png&quot;</span>) km = KM(n_clusters=<span class="synConstant">3</span>) km_target = km.fit_predict(data) plot_data(data, km_target, filename=<span class="synConstant">&quot;kmeans.png&quot;</span>) cm_result = cmeans(data.T, <span class="synConstant">3</span>, <span class="synConstant">2</span>, <span class="synConstant">0.003</span>, <span class="synConstant">10000</span>) plot_data(data, cm_result[<span class="synConstant">1</span>].T, filename=<span class="synConstant">&quot;cmeans_2.png&quot;</span>) gmm = GMM(n_components=<span class="synConstant">3</span>, max_iter=<span class="synConstant">1000</span>) gmm.fit(data) gmm_target = gmm.predict(data) gmm_target_proba = gmm.predict_proba(data) plot_data(data, gmm_target, filename=<span class="synConstant">&quot;gmm.png&quot;</span>) plot_data(data, gmm_target_proba, filename=<span class="synConstant">&quot;gmm_proba.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre> </div> <div class="section"> <h3>結果</h3> <div class="section"> <h4>オリジナルデータ</h4> <p><div style="text-align: center;"><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041207.png" alt="f:id:hayataka2049:20180306041207p:plain" title="f:id:hayataka2049:20180306041207p:plain" class="hatena-fotolife" itemprop="image"></span><br /> 元データ</div> これが元のデータです。できるだけこれに近いようなクラスタリング結果を得ることを目標とします。</p> </div> <div class="section"> <h4>k-means</h4> <p><div style="text-align: center;"><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041256.png" alt="f:id:hayataka2049:20180306041256p:plain" title="f:id:hayataka2049:20180306041256p:plain" class="hatena-fotolife" itemprop="image"></span><br /> k-means</div> 図の左側のクラスタは分離できていますが、右側は割と悲惨です。クラスタ同士が隣接していて細長い形だったりすると上手く行かないことが多いのがk-meansの特徴です。</p> </div> <div class="section"> <h4>c-means</h4> <p><div style="text-align: center;"><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041313.png" alt="f:id:hayataka2049:20180306041313p:plain" title="f:id:hayataka2049:20180306041313p:plain" class="hatena-fotolife" itemprop="image"></span><br /> Fuzzy c-means</div></p><p> こうして見るとc-meansは「ファジー理論を入れて境界を曖昧にしたk-means」という気がしてきます。実際アルゴリズムもそんな感じなんですけど。</p> </div> <div class="section"> <h4>GMM</h4> <p><div style="text-align: center;"><br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041716.png" alt="f:id:hayataka2049:20180306041716p:plain" title="f:id:hayataka2049:20180306041716p:plain" class="hatena-fotolife" itemprop="image"></span><br /> GMM-based clustering (hard)</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180306/20180306041818.png" alt="f:id:hayataka2049:20180306041818p:plain" title="f:id:hayataka2049:20180306041818p:plain" class="hatena-fotolife" itemprop="image"></span><br /> GMM-based clustering (soft)</div> 一見して「おお」って感じですね。k-means、c-meansと比較して、元データのラベルに近いクラスタリング結果が得られています(図の右側の2つのクラスタの境界が右肩上がりになっている)。まあ、ちょっと元データのラベルとはずれているんですが(右下の方はかなり怪しい)、普通はこちらの方がk-meansやc-meansより「良い」クラスタリング結果だ、と判断されることが多いでしょう。</p><p> どうしてこうなるのかというと、「irisのデータが正規分布していた」ということに尽きます。ま、アヤメの花びらの大きさとかのデータですから、正規分布しているんでしょう、きっと。</p><p> こうして見るとGMMの方が良さそうな気もしますが、「ちゃんと正規分布してるか」が怪しいとちょっと適用するのを躊躇うのと、あと計算コスト自体はk-meansより高いはずなので<a href="#f-947394f5" name="fn-947394f5" title="Fuzzy c-meansとどっちが良いかは未調査">*2</a>、いまいちk-meansと比べて使われていない、というのが実情に近いかもしれません。</p> </div> </div> <div class="section"> <h3>まとめ</h3> <p> GMMを使ってみたらけっこう良かったです。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-c5112e5f" name="f-c5112e5f" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">一次元なら平均と分散、多次元なら共分散みたいな話になってくるのだろうか?</span></p> <p class="footnote"><a href="#fn-947394f5" name="f-947394f5" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Fuzzy c-meansとどっちが良いかは未調査</span></p> </div> Tue, 06 Mar 2018 04:39:50 +0900 hatenablog://entry/17391345971622376100 python sklearn 統計 クラスタリング 主成分分析 機械学習 【python】正準相関分析(Canonical Correlation Analysis)を試してみる https://www.haya-programming.com/entry/2018/02/16/021314 <p> 正準相関分析を使うと、2つの多次元データ同士の関連性を分析できるらしい。</p><p> 面白そうなので試してみた。ちなみに正準相関はsklearn.cross_decomposition.CCAで使える。正準相関自体の解説はほとんどしないので、文中のリンクを参考にして欲しい<a href="#f-e0b910be" name="fn-e0b910be" title="正準相関でググって1ページ目に出てくるようなページばかり・・・">*1</a>。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#一応概要だけ">一応概要だけ</a></li> <li><a href="#ノイズに埋もれた波形を取り出す">ノイズに埋もれた波形を取り出す</a></li> <li><a href="#もうちょっとデータ分析っぽいことをしてみる">もうちょっとデータ分析っぽいことをしてみる</a><ul> <li><a href="#正準相関向きのデータを探すのは困難">正準相関向きのデータを探すのは困難</a></li> <li><a href="#作成したデータ">作成したデータ</a></li> <li><a href="#実験と結果">実験と結果</a></li> </ul> </li> </ul><p><span style="font-size: 80%">スポンサーリンク</span><br /> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p> <p><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-6261827798827777" data-ad-slot="1744230936" data-ad-format="auto" data-full-width-responsive="true"></ins><br /> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script><br /> </p> <div class="section"> <h3 id="一応概要だけ">一応概要だけ</h3> <p> 代表的な多変量解析の手法(といって良いのかどうか少し悩むけど)として、主成分分析や重回帰分析が存在する。</p> <ul> <li>主成分分析:一つの多変量データを直交するより少ない変数に縮約する</li> <li>重回帰分析:一つの多変量データを一つの単変量データに変換する</li> </ul><p> 主成分分析にしろ重回帰分析にしろ、変換の係数だったり行列だったりを求めてそれで変換するのが実際にやることである。</p><p> さて、正準相関は上の流れで説明すると、</p> <ul> <li>正準相関分析:二つの多変量データをそれぞれ直交するより少ない変数に縮約して、かつ二つの変換されたデータの間で相関を最大化する</li> </ul><p> という目的の分析である。主成分分析と重回帰を混ぜた感じ。</p><p> 気づいた人もいると思うけど、多変量vs多変量のデータでどちらかを単変量に分解して個別に重回帰で解くことも可能である。それに対するメリットとしては、</p> <ul> <li>個別に重回帰するより全体の構造みたいなものを捉えられる可能性がある</li> <li>個別に重回帰すると係数の数が全体でとても多くなるので解釈が面倒くさいが、一度次元を下げて直交した空間に持っていくことでそこが楽になる</li> </ul><p> というあたりがあり、要するに解釈性がいいということ。</p><p> この説明でもよくわからん、という人は、ニューラルネットのオートエンコーダーとか思い浮かべていただくとかえってわかりやすいかもしれない。</p> </div> <div class="section"> <h3 id="ノイズに埋もれた波形を取り出す">ノイズに埋もれた波形を取り出す</h3> <p> 参考URLの通りにやることにする。</p><p> 単一の信号源に複数のプローブを当てていて、それぞれに独立のノイズが乗って信号が埋もれてしまった・・・みたいな状況から元の信号を取り出そうとしているらしい。脳波計測とかで使えるのだろうか?</p><p> 参考URL:<a href="https://www.jstage.jst.go.jp/article/jnns/20/2/20_62/_pdf">https://www.jstage.jst.go.jp/article/jnns/20/2/20_62/_pdf</a></p><p> とりあえずこのようなコードを書き、</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> sklearn.cross_decomposition <span class="synPreProc">import</span> CCA <span class="synStatement">def</span> <span class="synIdentifier">plot_wave</span>(data, filename): fig, [ax1,ax2] = plt.subplots(<span class="synConstant">2</span>,<span class="synConstant">1</span>,figsize=(<span class="synConstant">16</span>,<span class="synConstant">9</span>)) ax1.plot(data[:,<span class="synConstant">0</span>], color=<span class="synConstant">&quot;b&quot;</span>) ax2.plot(data[:,<span class="synConstant">1</span>], color=<span class="synConstant">&quot;r&quot;</span>) plt.savefig(filename) <span class="synStatement">def</span> <span class="synIdentifier">gen_pulse_data</span>(): common_pulse = np.array([-<span class="synConstant">1</span>]*<span class="synConstant">50</span> + [<span class="synConstant">0</span>]*<span class="synConstant">50</span> + [<span class="synConstant">1</span>]*<span class="synConstant">50</span> + [<span class="synConstant">0</span>]*<span class="synConstant">50</span> + [-<span class="synConstant">1</span>]*<span class="synConstant">50</span> + [<span class="synConstant">0</span>]*<span class="synConstant">50</span> + [<span class="synConstant">1</span>]*<span class="synConstant">50</span> + [<span class="synConstant">0</span>]*<span class="synConstant">50</span>, dtype=np.float64) common_pulse += (np.random.random(common_pulse.shape) - <span class="synConstant">0.5</span>)*<span class="synConstant">0.1</span> noise1 = (np.random.random(common_pulse.shape) - <span class="synConstant">0.5</span>)*<span class="synConstant">50</span> noise2 = (np.random.random(common_pulse.shape) - <span class="synConstant">0.5</span>)*<span class="synConstant">50</span> data1 = np.vstack([common_pulse + noise1, common_pulse - noise1]).T data2 = np.vstack([common_pulse + noise2, common_pulse - noise2]).T <span class="synStatement">return</span> data1, data2 <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): X1, X2 = gen_pulse_data() plot_wave(X1, <span class="synConstant">&quot;X1.png&quot;</span>) plot_wave(X2, <span class="synConstant">&quot;X2.png&quot;</span>) cca = CCA(n_components=<span class="synConstant">2</span>) cca.fit(X1, X2) Y1 = cca.transform(X1) Y2 = cca.transform(X2) plot_wave(Y1, <span class="synConstant">&quot;Y1.png&quot;</span>) plot_wave(Y2, <span class="synConstant">&quot;Y2.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 実行する。</p><p> まずは元の信号。</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180216/20180216011146.png" alt="f:id:hayataka2049:20180216011146p:plain" title="f:id:hayataka2049:20180216011146p:plain" class="hatena-fotolife" itemprop="image"></span><br />  X1</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180216/20180216011150.png" alt="f:id:hayataka2049:20180216011150p:plain" title="f:id:hayataka2049:20180216011150p:plain" class="hatena-fotolife" itemprop="image"></span><br />  X2</p><p> わかる訳ねえな、という感じ。</p><p> CCAでX1とX2の相関が最大になるような変換を計算し、その変換に基いてX1を変換したものをY1とすると、</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180216/20180216011240.png" alt="f:id:hayataka2049:20180216011240p:plain" title="f:id:hayataka2049:20180216011240p:plain" class="hatena-fotolife" itemprop="image"></span><br />  Y1</p><p> こんな感じになった。Y2も同じようなものなので省略。ここでは2次元出しているが、元のパルス信号が1次元なので2次元目(下)はノイズだけ出ている。</p><p> まあ、上手く動いているのではないだろうか。</p> </div> <div class="section"> <h3 id="もうちょっとデータ分析っぽいことをしてみる">もうちょっとデータ分析っぽいことをしてみる</h3> <p> 如何せん、「ノイズに埋もれた信号を取り出せる!」というだけでは、データ分析っぽくなくて(個人的には)面白くない。正準相関自体はもっと色々なことができるはず。</p><p> ここで足を引っ張るのは「正準相関向きのサンプルデータが見つからない」ということである。</p> <div class="section"> <h4 id="正準相関向きのデータを探すのは困難">正準相関向きのデータを探すのは困難</h4> <p> 2つの多次元データが対応しているようなデータで、適当にわかりやすいものがあれば良いのだが・・・なかなか良いデータがない。上に挙げた解説論文でも、「知名度は低い」とか書かれちゃってるし、正準相関自体、ニッチな感じがする。そこが素敵なのだが。</p><p> 一応、ネット上にある解説例だと、</p> <ul> <li><a href="http://www.snap-tck.com/room04/c01/stat/stat19/stat1901.html">&#x7D71;&#x8A08;&#x5B66;&#x5165;&#x9580;&minus;&#x7B2C;19&#x7AE0;</a></li> </ul><p> 医学の分野で、肝機能の検査値(複数)と腎機能の検査値(複数)の対応を見るとか、</p> <ul> <li><a href="http://ogasun.la.coocan.jp/hanbetsubunseki.pdf">http://ogasun.la.coocan.jp/hanbetsubunseki.pdf</a></li> </ul><p> 中学生の体格(身長、体重、座高とか)と運動能力(50m走、走り幅跳びとか)の対応を見るとか、</p><p> そういう感じのことをやっているのだが、この手のデータを探してくるのがまず面倒くさいし、見つけてもプログラムに流し込めるようにするまでがまた苦行だろうな、ということは容易に想像できるのである。</p><p> この点で悩んで、この記事も一週間くらい出すか出さないか迷ってたんだけど、やることにした。ただ、結局良いデータは見つからなかったので、それっぽくでっちあげることにした。</p> </div> <div class="section"> <h4 id="作成したデータ">作成したデータ</h4> <p> ある架空の中学校で集計したという設定の、20人の生徒のデータである。「学外での勉強や取り組み」と「学校の成績」が対応付いている。</p><p> 「学外での勉強や取り組み」には、</p> <ul> <li>一ヶ月に何冊読書するか</li> <li>一年に何回博物館に行くか</li> <li>毎週何日塾に通っているか</li> <li>毎日何時間自習しているか</li> </ul><p> の4つの変数がある。一方、「学校の成績」は、</p> <ul> <li>国語</li> <li>数学</li> <li>社会</li> <li>理科</li> <li>英語</li> </ul><p> の5つの科目があり、5段階評価で成績が付く。</p><p> 本来であれば適当に線形モデルでも作ってあげて数字を作るべきところだが、面倒くさいので私の想像で適当に埋めた(ツッコミポイント)。</p><p> 一応、次のような方針を考え、それに沿ったデータになるようにでっちあげた・・・つもり。</p> <ul> <li>読書量と国語の成績は比例する</li> <li>博物館に行った回数と社会、理科の成績は比例する</li> <li>塾に通う頻度、自習時間は成績全体に影響を及ぼす</li> </ul><p> よって、こういう結果が出てくるか、という勝負になる。</p> </div> <div class="section"> <h4 id="実験と結果">実験と結果</h4> <p> こういうプログラムを書いた。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> scipy.stats <span class="synPreProc">import</span> pearsonr <span class="synPreProc">from</span> sklearn.cross_decomposition <span class="synPreProc">import</span> CCA <span class="synStatement">def</span> <span class="synIdentifier">gen_data</span>(): <span class="synComment"># X1:</span> <span class="synComment"># 毎月何冊の本を読むか,</span> <span class="synComment"># 一年に何回博物館に行くか,</span> <span class="synComment"># 塾に週何日通うか,</span> <span class="synComment"># 毎日何時間自習するか</span> X1 = np.array([[<span class="synConstant">1</span>,<span class="synConstant">0</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>], [<span class="synConstant">3</span>,<span class="synConstant">2</span>,<span class="synConstant">4</span>,<span class="synConstant">2</span>], [<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">2</span>,<span class="synConstant">0</span>], [<span class="synConstant">9</span>,<span class="synConstant">4</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>], [<span class="synConstant">1</span>,<span class="synConstant">1</span>,<span class="synConstant">3</span>,<span class="synConstant">1</span>], [<span class="synConstant">8</span>,<span class="synConstant">1</span>,<span class="synConstant">6</span>,<span class="synConstant">3</span>], [<span class="synConstant">0</span>,<span class="synConstant">9</span>,<span class="synConstant">7</span>,<span class="synConstant">8</span>], [<span class="synConstant">2</span>,<span class="synConstant">2</span>,<span class="synConstant">4</span>,<span class="synConstant">1</span>], [<span class="synConstant">5</span>,<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">1</span>], [<span class="synConstant">2</span>,<span class="synConstant">0</span>,<span class="synConstant">4</span>,<span class="synConstant">0</span>], [<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">7</span>,<span class="synConstant">8</span>], [<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">2</span>,<span class="synConstant">2</span>], [<span class="synConstant">5</span>,<span class="synConstant">1</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>], [<span class="synConstant">1</span>,<span class="synConstant">1</span>,<span class="synConstant">5</span>,<span class="synConstant">2</span>], [<span class="synConstant">8</span>,<span class="synConstant">6</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>], [<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">0</span>,<span class="synConstant">1</span>], [<span class="synConstant">6</span>,<span class="synConstant">1</span>,<span class="synConstant">3</span>,<span class="synConstant">1</span>], [<span class="synConstant">2</span>,<span class="synConstant">0</span>,<span class="synConstant">3</span>,<span class="synConstant">1</span>], [<span class="synConstant">4</span>,<span class="synConstant">8</span>,<span class="synConstant">5</span>,<span class="synConstant">3</span>], [<span class="synConstant">5</span>,<span class="synConstant">0</span>,<span class="synConstant">1</span>,<span class="synConstant">1</span>]]) <span class="synComment"># X2:</span> <span class="synComment"># 国語,数学,社会,理科,英語の成績</span> X2 = np.array([[<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>], [<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>], [<span class="synConstant">2</span>,<span class="synConstant">2</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">2</span>], [<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>], [<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>], [<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>], [<span class="synConstant">3</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>], [<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">3</span>], [<span class="synConstant">5</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>], [<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>], [<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>], [<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">3</span>], [<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>], [<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>], [<span class="synConstant">5</span>,<span class="synConstant">3</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">3</span>], [<span class="synConstant">2</span>,<span class="synConstant">2</span>,<span class="synConstant">2</span>,<span class="synConstant">1</span>,<span class="synConstant">2</span>], [<span class="synConstant">5</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>,<span class="synConstant">4</span>], [<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">3</span>], [<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>,<span class="synConstant">5</span>], [<span class="synConstant">5</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>,<span class="synConstant">3</span>]]) <span class="synStatement">return</span> X1, X2 <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): X1, X2 = gen_data() cca = CCA(n_components=<span class="synConstant">4</span>) cca.fit(X1, X2) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;Correlation Coefficient&quot;</span>) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">4</span>): <span class="synIdentifier">print</span>(<span class="synConstant">&quot;{0}:{1:.4f}&quot;</span>.format(i, pearsonr(cca.x_scores_[:,i], cca.y_scores_[:,i])[<span class="synConstant">0</span>])) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;&quot;</span>) np.set_printoptions(formatter={<span class="synConstant">'float'</span>: <span class="synConstant">'{: 0.4f}'</span>.format}) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;X1 loadings&quot;</span>) <span class="synIdentifier">print</span>(cca.x_loadings_.T) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;&quot;</span>) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;X2 loadings&quot;</span>) <span class="synIdentifier">print</span>(cca.y_loadings_.T) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> 「学外での勉強や取り組み」=X1と「学校の成績」=X2を4次元の空間上に写像して相関を最大化する、という問題を解かせる。軸同士は直交していて無相関なので、写像したデータの各軸の値同士の相関だけ見てやれば良い。写像したデータは、cca.x(or y)_scores_かcca.transform(X1(or X2))で取得できる<a href="#f-57cab790" name="fn-57cab790" title="今回はどちらも同じ値が返るが、transformだと学習時とは違うデータも入れられる">*2</a>。</p><p> あとはX1とX2の各成分が、写像先の各軸にどれだけ寄与しているかがわかれば良い。そのためにはcca.x(or y)_loadings_を見る。転置した方が見やすいのでそうしている。</p><p> こうして見ると、PCAに似ている。実際、CCAとPCAは親戚らしい。ま、あまり理論的な話に深入りしてもボロが出るので、これくらいにしておく。</p><p> さて、結果はこのようになった。</p> <pre class="code" data-lang="" data-unlink>Correlation Coefficient 0:0.9558 1:0.8978 2:0.5980 3:0.2927 X1 loadings [[-0.4224 0.4244 1.0047 0.7353] [ 0.9326 0.4315 0.2511 0.3450] [ 0.2350 0.9269 -0.1714 -0.2514] [-0.4357 0.4062 -0.0634 0.8007]] X2 loadings [[-0.0558 0.7802 0.6574 0.6206 0.8044] [ 0.9392 0.5254 0.4702 0.3366 0.4271] [-0.2184 -0.0705 0.9353 0.5442 -0.3528] [-0.4310 0.0676 -0.2136 -0.8751 0.0008]]</pre><p> まず見るべきはCorrelation Coefficientで、写像先の空間の軸にどれだけ相関(=やった意味)があるかを示している。0,1,2次元目はまあまあ強い相関だが、3次元目は相関係数0.3じゃ大した意味はなさそうだな、という風に解釈しておく。</p><p> 次にX1 loadingsとX2 loadingsを見る。X1 loadingsは4*4、X2 loadingsは4*5で、つまり行が写像先の軸、列が元の空間の軸に対応するように表示している。</p><p> X1 loadingsの各行を見ていくと、</p> <ul> <li>1行目</li> </ul><p> 塾と自習に熱心</p> <ul> <li>2行目</li> </ul><p> 読書</p> <ul> <li>3行目</li> </ul><p> 博物館</p> <ul> <li>4行目</li> </ul><p> 自習と博物館だけ?</p><p> なんとか解釈できる。数字がでかいところだけ重視するのがこつ。X2 loadingsも同様にやると、</p> <ul> <li>1行目</li> </ul><p> 国語以外のすべて。国語にはほぼ中立。特に強いのは英語</p> <ul> <li>2行目</li> </ul><p> 国語。他もそれなりに</p> <ul> <li>3行目</li> </ul><p> 社会と理科</p> <ul> <li>4行目</li> </ul><p> 理科にとてもネガティブ。全体的にネガティブな感じ</p><p> ここまで出揃えば後はなんとかなる。このデータを作った方針を再掲する。</p> <ul> <li>読書量と国語の成績は比例する</li> <li>博物館に行った回数と社会、理科の成績は比例する</li> <li>塾に通う頻度、自習時間は成績全体に影響を及ぼす</li> </ul><p> 0次元目は「塾に通う頻度、自習時間は成績全体に影響を及ぼす」とに、1次元目は「読書量と国語の成績は比例する」に、2次元目は「博物館に行った回数と社会、理科の成績は比例する」に対応していることがわかり、まあ妥当な結果なんじゃないの、という気はする。相関係数の低い3次元目はそこまで重視する必要はない。</p><p> 今回は先に方針を決めてデータをでっち上げたのであまり感動がないような気もするが、実際はデータにどんな構造があるのかは分析してみないとわからない。その構造を理解する上で正準相関が役に立つことは、上の例でなんとなく理解できた。</p> </div> </div><div class="footnote"> <p class="footnote"><a href="#fn-e0b910be" name="f-e0b910be" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">正準相関でググって1ページ目に出てくるようなページばかり・・・</span></p> <p class="footnote"><a href="#fn-57cab790" name="f-57cab790" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">今回はどちらも同じ値が返るが、transformだと学習時とは違うデータも入れられる</span></p> </div> Fri, 16 Feb 2018 02:13:14 +0900 hatenablog://entry/17391345971616763473 python sklearn 統計 主成分分析 機械学習 【python】95%信頼楕円/確率楕円を描画する https://www.haya-programming.com/entry/2018/02/14/235500 <p> 「ライブラリあるやろw」と思ったら、なくて顔面蒼白になった。</p><p> しょうがないから調べて実装した。</p> <div class="section"> <h3>理論的なもの</h3> <p> ちゃんと数式を書いて説明する気概がないので、アバウトに説明する。</p><p> 適当な二次元正規分布のデータがあるとする。PCAと同じ要領で分散共分散行列を対角化する。</p><p> 対角化した行列の対角成分(=固有値)は、データを軸同士の相関がないような空間に変換して(=要するにぐるっと回して)あげたときの変換先の軸上における分散である。</p><p> 分散がわかれば、一次元のときの信頼区間的なものがわかる。それを決めるためのデータの中心位置からの距離にはマハラノビス距離という名前が付いている。そして、これのニ乗は<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cchi%5E2" alt=" \chi^2"/>分布になるので、けっきょく累積確率だけ決めれば適当に定まる、ということがわかる<a href="#f-48768066" name="fn-48768066" title="てきとーすぎる説明だな">*1</a>。相関がなくなるように軸を作ったので、各軸ごとで求めてやれば(その軸上での)信頼楕円の径がわかる。</p><p> この段階で楕円の幅と高さがわかっていることになるので、あとは適当に回転角を計算すると、</p> <ul> <li>楕円の中心位置(単純に全データの平均で良い)</li> <li>幅と高さ</li> <li>回転角</li> </ul><p> <br />  がわかることになり、楕円が描ける。</p> </div> <div class="section"> <h3>実装</h3> <p> 実装したものをそのまま貼っておきます。説明はしないので、参考にしたければしてください。</p><p> 一応入力データを色々変えてみておかしくなさそうなことは確認していますが、ミスがないとは言い切れません。ご注意ください。見つけたら指摘して頂けると嬉しいです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># coding: UTF-8</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> scipy.stats <span class="synPreProc">import</span> chi2 <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> matplotlib.patches <span class="synPreProc">import</span> Ellipse <span class="synStatement">class</span> <span class="synIdentifier">ConfidenceEllipse</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, data, p=<span class="synConstant">0.95</span>): self.data = data self.p = p self.means = np.mean(data, axis=<span class="synConstant">0</span>) self.cov = np.cov(data[:,<span class="synConstant">0</span>], data[:,<span class="synConstant">1</span>]) lambdas, vecs = np.linalg.eigh(self.cov) order = lambdas.argsort()[::-<span class="synConstant">1</span>] lambdas, vecs = lambdas[order], vecs[:,order] c = np.sqrt(chi2.ppf(self.p, <span class="synConstant">2</span>)) self.w, self.h = <span class="synConstant">2</span> * c * np.sqrt(lambdas) self.theta = np.degrees(np.arctan( ((lambdas[<span class="synConstant">0</span>] - lambdas[<span class="synConstant">1</span>])/self.cov[<span class="synConstant">0</span>,<span class="synConstant">1</span>]))) <span class="synStatement">def</span> <span class="synIdentifier">get_params</span>(self): <span class="synStatement">return</span> self.means, self.w, self.h, self.theta <span class="synStatement">def</span> <span class="synIdentifier">get_patch</span>(self, line_color=<span class="synConstant">&quot;black&quot;</span>, face_color=<span class="synConstant">&quot;none&quot;</span>, alpha=<span class="synConstant">0</span>): el = Ellipse(xy=self.means, width=self.w, height=self.h, angle=self.theta, color=line_color, alpha=alpha) el.set_facecolor(face_color) <span class="synStatement">return</span> el <span class="synStatement">def</span> <span class="synIdentifier">gen_data</span>(): <span class="synStatement">return</span> np.random.multivariate_normal([<span class="synConstant">3</span>,<span class="synConstant">3</span>], [[<span class="synConstant">0.3</span>,-<span class="synConstant">0.2</span>],[-<span class="synConstant">0.2</span>,<span class="synConstant">1</span>]], size=<span class="synConstant">100</span>) <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): data = gen_data() fig = plt.figure() ax = fig.add_subplot(<span class="synConstant">1</span>,<span class="synConstant">1</span>,<span class="synConstant">1</span>) ax.scatter(data[:,<span class="synConstant">0</span>], data[:,<span class="synConstant">1</span>], color=<span class="synConstant">&quot;b&quot;</span>, marker=<span class="synConstant">&quot;.&quot;</span>, s=<span class="synConstant">3</span>) el = ConfidenceEllipse(data, p=<span class="synConstant">0.95</span>) ax.add_artist(el.get_patch(face_color=<span class="synConstant">&quot;blue&quot;</span>, alpha=<span class="synConstant">0.5</span>)) plt.savefig(<span class="synConstant">&quot;img.png&quot;</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: main() </pre><p> こんな感じの絵が出てきます。</p><p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hayataka2049/20180214/20180214235058.png" alt="f:id:hayataka2049:20180214235058p:plain" title="f:id:hayataka2049:20180214235058p:plain" class="hatena-fotolife" itemprop="image"></span></p><p> 正しいはずだけど、詳しくチェックはしていないので、自己責任でご利用ください。</p> </div> <div class="section"> <h3>参考文献</h3> <ul> <li><a href="https://stackoverflow.com/questions/20126061/creating-a-confidence-ellipses-in-a-sccatterplot-using-matplotlib">python - Creating a Confidence Ellipses in a sccatterplot using matplotlib - Stack Overflow</a></li> <li><a href="http://d.hatena.ne.jp/natsutan/20110421/1303344155">&#x78BA;&#x7387;&#x9577;&#x5186;&#x3092;&#x56F3;&#x3067;&#x793A;&#x3059;&#x3002; - &#x3071;&#x305F;&#x3078;&#x306D;&#xFF01;</a></li> <li><a href="http://www.snap-tck.com/room04/c01/stat/stat09/stat0904.html">&#x7D71;&#x8A08;&#x5B66;&#x5165;&#x9580;&minus;&#x7B2C;9&#x7AE0;</a></li> <li><a href="https://scipython.com/book/chapter-7-matplotlib/examples/bmi-data-with-confidence-ellipses/">BMI data with confidence ellipses</a></li> </ul> </div><div class="footnote"> <p class="footnote"><a href="#fn-48768066" name="f-48768066" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">てきとーすぎる説明だな</span></p> </div> Wed, 14 Feb 2018 23:55:00 +0900 hatenablog://entry/17391345971616455844 python scipy numpy 統計 matplotlib 主成分分析