การวิเคราะห์องค์ประกอบหลักใน Python


112

ฉันต้องการใช้การวิเคราะห์องค์ประกอบหลัก (PCA) สำหรับการลดขนาด มี numpy หรือ scipy อยู่แล้วหรือต้องม้วนเองโดยใช้numpy.linalg.eigh?

ฉันไม่เพียงต้องการใช้การสลายตัวของค่าเอกพจน์ (SVD) เพราะข้อมูลอินพุตของฉันค่อนข้างมีมิติสูง (~ 460 มิติ) ดังนั้นฉันคิดว่า SVD จะช้ากว่าการคำนวณหาค่าเอกพจน์ของเมทริกซ์ความแปรปรวนร่วม

ฉันหวังว่าจะได้พบกับการใช้งานที่แก้ไขจุดบกพร่องไว้ล่วงหน้าซึ่งได้ทำการตัดสินใจที่ถูกต้องแล้วว่าจะใช้วิธีใดและอาจเป็นการเพิ่มประสิทธิภาพอื่น ๆ ที่ฉันไม่รู้

คำตอบ:


28

คุณอาจมีลักษณะที่MDP

ฉันไม่ได้มีโอกาสทดสอบด้วยตัวเอง แต่ฉันได้บุ๊กมาร์กไว้สำหรับฟังก์ชัน PCA


8
MDP ไม่ได้รับการดูแลตั้งแต่ปี 2555 ดูเหมือนจะไม่ใช่ทางออกที่ดีที่สุด
Marc Garcia

อัปเดตล่าสุดตั้งแต่วันที่ 09.03.2016 แต่โปรดทราบว่า ir เป็นเพียงรุ่นแก้ไขข้อบกพร่องเท่านั้น:Note that from this release MDP is in maintenance mode. 13 years after its first public release, MDP has reached full maturity and no new features are planned in the future.
Gabriel

65

หลายเดือนต่อมานี่คือ PCA ระดับเล็กและรูปภาพ:

#!/usr/bin/env python
""" a small class for Principal Component Analysis
Usage:
    p = PCA( A, fraction=0.90 )
In:
    A: an array of e.g. 1000 observations x 20 variables, 1000 rows x 20 columns
    fraction: use principal components that account for e.g.
        90 % of the total variance

Out:
    p.U, p.d, p.Vt: from numpy.linalg.svd, A = U . d . Vt
    p.dinv: 1/d or 0, see NR
    p.eigen: the eigenvalues of A*A, in decreasing order (p.d**2).
        eigen[j] / eigen.sum() is variable j's fraction of the total variance;
        look at the first few eigen[] to see how many PCs get to 90 %, 95 % ...
    p.npc: number of principal components,
        e.g. 2 if the top 2 eigenvalues are >= `fraction` of the total.
        It's ok to change this; methods use the current value.

Methods:
    The methods of class PCA transform vectors or arrays of e.g.
    20 variables, 2 principal components and 1000 observations,
    using partial matrices U' d' Vt', parts of the full U d Vt:
    A ~ U' . d' . Vt' where e.g.
        U' is 1000 x 2
        d' is diag([ d0, d1 ]), the 2 largest singular values
        Vt' is 2 x 20.  Dropping the primes,

    d . Vt      2 principal vars = p.vars_pc( 20 vars )
    U           1000 obs = p.pc_obs( 2 principal vars )
    U . d . Vt  1000 obs, p.obs( 20 vars ) = pc_obs( vars_pc( vars ))
        fast approximate A . vars, using the `npc` principal components

    Ut              2 pcs = p.obs_pc( 1000 obs )
    V . dinv        20 vars = p.pc_vars( 2 principal vars )
    V . dinv . Ut   20 vars, p.vars( 1000 obs ) = pc_vars( obs_pc( obs )),
        fast approximate Ainverse . obs: vars that give ~ those obs.


Notes:
    PCA does not center or scale A; you usually want to first
        A -= A.mean(A, axis=0)
        A /= A.std(A, axis=0)
    with the little class Center or the like, below.

See also:
    http://en.wikipedia.org/wiki/Principal_component_analysis
    http://en.wikipedia.org/wiki/Singular_value_decomposition
    Press et al., Numerical Recipes (2 or 3 ed), SVD
    PCA micro-tutorial
    iris-pca .py .png

"""

from __future__ import division
import numpy as np
dot = np.dot
    # import bz.numpyutil as nu
    # dot = nu.pdot

__version__ = "2010-04-14 apr"
__author_email__ = "denis-bz-py at t-online dot de"

#...............................................................................
class PCA:
    def __init__( self, A, fraction=0.90 ):
        assert 0 <= fraction <= 1
            # A = U . diag(d) . Vt, O( m n^2 ), lapack_lite --
        self.U, self.d, self.Vt = np.linalg.svd( A, full_matrices=False )
        assert np.all( self.d[:-1] >= self.d[1:] )  # sorted
        self.eigen = self.d**2
        self.sumvariance = np.cumsum(self.eigen)
        self.sumvariance /= self.sumvariance[-1]
        self.npc = np.searchsorted( self.sumvariance, fraction ) + 1
        self.dinv = np.array([ 1/d if d > self.d[0] * 1e-6  else 0
                                for d in self.d ])

    def pc( self ):
        """ e.g. 1000 x 2 U[:, :npc] * d[:npc], to plot etc. """
        n = self.npc
        return self.U[:, :n] * self.d[:n]

    # These 1-line methods may not be worth the bother;
    # then use U d Vt directly --

    def vars_pc( self, x ):
        n = self.npc
        return self.d[:n] * dot( self.Vt[:n], x.T ).T  # 20 vars -> 2 principal

    def pc_vars( self, p ):
        n = self.npc
        return dot( self.Vt[:n].T, (self.dinv[:n] * p).T ) .T  # 2 PC -> 20 vars

    def pc_obs( self, p ):
        n = self.npc
        return dot( self.U[:, :n], p.T )  # 2 principal -> 1000 obs

    def obs_pc( self, obs ):
        n = self.npc
        return dot( self.U[:, :n].T, obs ) .T  # 1000 obs -> 2 principal

    def obs( self, x ):
        return self.pc_obs( self.vars_pc(x) )  # 20 vars -> 2 principal -> 1000 obs

    def vars( self, obs ):
        return self.pc_vars( self.obs_pc(obs) )  # 1000 obs -> 2 principal -> 20 vars


class Center:
    """ A -= A.mean() /= A.std(), inplace -- use A.copy() if need be
        uncenter(x) == original A . x
    """
        # mttiw
    def __init__( self, A, axis=0, scale=True, verbose=1 ):
        self.mean = A.mean(axis=axis)
        if verbose:
            print "Center -= A.mean:", self.mean
        A -= self.mean
        if scale:
            std = A.std(axis=axis)
            self.std = np.where( std, std, 1. )
            if verbose:
                print "Center /= A.std:", self.std
            A /= self.std
        else:
            self.std = np.ones( A.shape[-1] )
        self.A = A

    def uncenter( self, x ):
        return np.dot( self.A, x * self.std ) + np.dot( x, self.mean )


#...............................................................................
if __name__ == "__main__":
    import sys

    csv = "iris4.csv"  # wikipedia Iris_flower_data_set
        # 5.1,3.5,1.4,0.2  # ,Iris-setosa ...
    N = 1000
    K = 20
    fraction = .90
    seed = 1
    exec "\n".join( sys.argv[1:] )  # N= ...
    np.random.seed(seed)
    np.set_printoptions( 1, threshold=100, suppress=True )  # .1f
    try:
        A = np.genfromtxt( csv, delimiter="," )
        N, K = A.shape
    except IOError:
        A = np.random.normal( size=(N, K) )  # gen correlated ?

    print "csv: %s  N: %d  K: %d  fraction: %.2g" % (csv, N, K, fraction)
    Center(A)
    print "A:", A

    print "PCA ..." ,
    p = PCA( A, fraction=fraction )
    print "npc:", p.npc
    print "% variance:", p.sumvariance * 100

    print "Vt[0], weights that give PC 0:", p.Vt[0]
    print "A . Vt[0]:", dot( A, p.Vt[0] )
    print "pc:", p.pc()

    print "\nobs <-> pc <-> x: with fraction=1, diffs should be ~ 0"
    x = np.ones(K)
    # x = np.ones(( 3, K ))
    print "x:", x
    pc = p.vars_pc(x)  # d' Vt' x
    print "vars_pc(x):", pc
    print "back to ~ x:", p.pc_vars(pc)

    Ax = dot( A, x.T )
    pcx = p.obs(x)  # U' d' Vt' x
    print "Ax:", Ax
    print "A'x:", pcx
    print "max |Ax - A'x|: %.2g" % np.linalg.norm( Ax - pcx, np.inf )

    b = Ax  # ~ back to original x, Ainv A x
    back = p.vars(b)
    print "~ back again:", back
    print "max |back - x|: %.2g" % np.linalg.norm( back - x, np.inf )

# end pca.py

ใส่คำอธิบายภาพที่นี่


3
Fyinfo, มีการพูดคุยที่ดีในการPCA ที่แข็งแกร่งโดยซี Caramanis มกราคม 2011
Denis

รหัสนี้จะส่งออกภาพนั้นหรือไม่ (Iris PCA) หากไม่เป็นเช่นนั้นคุณสามารถโพสต์ทางเลือกอื่นได้หรือไม่โดยให้รูปภาพนั้นออกมา IM มีปัญหาในการแปลงรหัสนี้เป็น c ++ เพราะฉันใหม่ใน python :)
Orvyl

44

การใช้ PCA numpy.linalg.svdนั้นง่ายมาก นี่คือการสาธิตง่ายๆ:

import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import lena

# the underlying signal is a sinusoidally modulated image
img = lena()
t = np.arange(100)
time = np.sin(0.1*t)
real = time[:,np.newaxis,np.newaxis] * img[np.newaxis,...]

# we add some noise
noisy = real + np.random.randn(*real.shape)*255

# (observations, features) matrix
M = noisy.reshape(noisy.shape[0],-1)

# singular value decomposition factorises your data matrix such that:
# 
#   M = U*S*V.T     (where '*' is matrix multiplication)
# 
# * U and V are the singular matrices, containing orthogonal vectors of
#   unit length in their rows and columns respectively.
#
# * S is a diagonal matrix containing the singular values of M - these 
#   values squared divided by the number of observations will give the 
#   variance explained by each PC.
#
# * if M is considered to be an (observations, features) matrix, the PCs
#   themselves would correspond to the rows of S^(1/2)*V.T. if M is 
#   (features, observations) then the PCs would be the columns of
#   U*S^(1/2).
#
# * since U and V both contain orthonormal vectors, U*V.T is equivalent 
#   to a whitened version of M.

U, s, Vt = np.linalg.svd(M, full_matrices=False)
V = Vt.T

# PCs are already sorted by descending order 
# of the singular values (i.e. by the
# proportion of total variance they explain)

# if we use all of the PCs we can reconstruct the noisy signal perfectly
S = np.diag(s)
Mhat = np.dot(U, np.dot(S, V.T))
print "Using all PCs, MSE = %.6G" %(np.mean((M - Mhat)**2))

# if we use only the first 20 PCs the reconstruction is less accurate
Mhat2 = np.dot(U[:, :20], np.dot(S[:20, :20], V[:,:20].T))
print "Using first 20 PCs, MSE = %.6G" %(np.mean((M - Mhat2)**2))

fig, [ax1, ax2, ax3] = plt.subplots(1, 3)
ax1.imshow(img)
ax1.set_title('true image')
ax2.imshow(noisy.mean(0))
ax2.set_title('mean of noisy images')
ax3.imshow((s[0]**(1./2) * V[:,0]).reshape(img.shape))
ax3.set_title('first spatial PC')
plt.show()

2
ฉันรู้ว่าฉันมาสายเล็กน้อยที่นี่ แต่ OP ขอโซลูชันที่หลีกเลี่ยงการสลายตัวของค่าเอกพจน์โดยเฉพาะ
Alex A.

1
@ อเล็กซ์ฉันตระหนักดี แต่ฉันเชื่อว่า SVD ยังคงเป็นแนวทางที่ถูกต้อง มันควรจะเร็วพอสำหรับความต้องการของ OP ได้อย่างง่ายดาย (ตัวอย่างของฉันด้านบนที่มีขนาด 262144 ใช้เวลาเพียง ~ 7.5 วินาทีในแล็ปท็อปปกติ) และมีความเสถียรทางตัวเลขมากกว่าวิธีการจัดองค์ประกอบแบบ eigend (ดูความคิดเห็นของ dwf ด้านล่าง) ฉันทราบด้วยว่าคำตอบที่ยอมรับนั้นใช้ SVD เช่นกัน!
ali_m

ฉันไม่เห็นด้วยที่ SVD เป็นหนทางไปฉันแค่บอกว่าคำตอบไม่ได้ตอบคำถามตามที่ระบุไว้ในคำถาม เป็นคำตอบที่ดี แต่ทำได้ดี
Alex A.

5
@ อเล็กซ์พอใช้. ฉันคิดว่านี่เป็นอีกตัวแปรหนึ่งของปัญหา XY - OP กล่าวว่าเขาไม่ต้องการโซลูชันที่ใช้ SVD เพราะเขาคิดว่า SVD จะช้าเกินไปอาจจะยังไม่ได้ลอง ในกรณีเช่นนี้โดยส่วนตัวแล้วฉันคิดว่าการอธิบายว่าคุณจะจัดการกับปัญหาที่กว้างขึ้นนั้นมีประโยชน์มากกว่าการตอบคำถามในรูปแบบเดิมที่แคบกว่า
ali_m

svdแล้วส่งคืนsตามลำดับจากมากไปหาน้อยแล้วเท่าที่เอกสารจะดำเนินไป (อาจจะไม่ใช่กรณีนี้ในปี 2555 แต่ปัจจุบันเป็นเช่นนั้น)
Etienne Bruines

34

คุณสามารถใช้ sklearn:

import sklearn.decomposition as deco
import numpy as np

x = (x - np.mean(x, 0)) / np.std(x, 0) # You need to normalize your data first
pca = deco.PCA(n_components) # n_components is the components number after reduction
x_r = pca.fit(x).transform(x)
print ('explained variance (first %d components): %.2f'%(n_components, sum(pca.explained_variance_ratio_)))

โหวตขึ้นเพราะสิ่งนี้ใช้ได้ผลดีสำหรับฉัน - ฉันมีมากกว่า 460 มิติและแม้ว่า sklearn จะใช้ SVD และคำถามที่ขอไม่ใช่ SVD แต่ฉันคิดว่า 460 มิติน่าจะโอเค
Dan Stowell

คุณอาจต้องการลบคอลัมน์ที่มีค่าคงที่ (std = 0) สำหรับสิ่งนั้นคุณควรใช้: remove_cols = np.where (np.all (x == np.mean (x, 0), 0)) [0] แล้ว x = np.delete (x, remove_cols, 1)
Noam Peled

31

matplotlib.mlabมีการดำเนินงาน PCA


5
อัปเดตลิงก์สำหรับPCA ของ matplotlibแล้ว
พัฒนา

3
การใช้ matplotlib.mlab ของ PCA ใช้ SVD
Aman

3
นี่คือคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับฟังก์ชันและวิธีการใช้งาน
Dolan Antenucci

14

SVD ควรทำงานได้ดีกับ 460 ขนาด ใช้เวลาประมาณ 7 วินาทีบนเน็ตบุ๊ก Atom ของฉัน วิธี eig () ใช้เวลามากกว่า (ตามที่ควรจะใช้การดำเนินการจุดลอยตัวมากกว่า) และมักจะมีความแม่นยำน้อยกว่า

หากคุณมีน้อยกว่า 460 ตัวอย่างสิ่งที่คุณต้องการทำคือทำทแยงมุมเมทริกซ์กระจาย (x - datamean) ^ T (x - ค่าเฉลี่ย) โดยสมมติว่าจุดข้อมูลของคุณเป็นคอลัมน์แล้วคูณทางซ้ายด้วย (x - datamean) ซึ่งอาจเร็วกว่าในกรณีที่คุณมีมิติข้อมูลมากกว่าข้อมูล


คุณสามารถอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับเคล็ดลับนี้ได้หรือไม่เมื่อคุณมีมิติข้อมูลมากกว่าข้อมูล
mrgloom

1
โดยทั่วไปคุณจะถือว่า eigenvectors เป็นการรวมเชิงเส้นของเวกเตอร์ข้อมูล ดู Sirovich (1987) "ความปั่นป่วนและพลวัตของโครงสร้างที่สอดคล้องกัน"
dwf

11

คุณสามารถ "ม้วน" ของคุณเองได้อย่างง่ายดายโดยใช้scipy.linalg(สมมติว่าเป็นชุดข้อมูลที่มีศูนย์กลางdata):

covmat = data.dot(data.T)
evs, evmat = scipy.linalg.eig(covmat)

จากนั้นevsเป็นค่าลักษณะเฉพาะของคุณและevmatเป็นเมทริกซ์การฉายของคุณ

หากคุณต้องการที่จะให้dมิติใช้ครั้งแรกdค่าลักษณะเฉพาะและเป็นครั้งแรกdeigenvectors

เนื่องจากscipy.linalgมีการสลายตัวและทำให้การคูณเมทริกซ์เป็นจำนวนมากคุณต้องการอะไรอีก?


เมทริกซ์ cov คือ np.dot (data.T, data, out = covmat) โดยที่ข้อมูลต้องเป็นเมทริกซ์กึ่งกลาง
mrgloom

2
คุณควรดูความคิดเห็นของ @ dwf เกี่ยวกับคำตอบนี้สำหรับอันตรายจากการใช้eig()เมทริกซ์ความแปรปรวนร่วม
Alex A.

8

ฉันเพิ่งเสร็จสิ้นการอ่านหนังสือเครื่องเรียนรู้: การขั้นตอนมุมมอง ตัวอย่างโค้ดทั้งหมดในหนังสือนี้เขียนโดย Python (และเกือบจะมี Numpy) ข้อมูลโค้ดของchatper10.2 การวิเคราะห์ส่วนประกอบหลักอาจคุ้มค่ากับการอ่าน ใช้ numpy.linalg.eig
อย่างไรก็ตามฉันคิดว่า SVD สามารถรองรับขนาด 460 * 460 ได้เป็นอย่างดี ฉันได้คำนวณ SVD 6500 * 6500 ด้วย numpy / scipy.linalg.svd บนพีซีรุ่นเก่ามาก: Pentium III 733mHz พูดตามตรงสคริปต์ต้องการหน่วยความจำจำนวนมาก (ประมาณ 1.xG) และเวลามาก (ประมาณ 30 นาที) เพื่อให้ได้ผลลัพธ์ SVD แต่ฉันคิดว่า 460 * 460 บนพีซีสมัยใหม่จะไม่เป็นปัญหาใหญ่เว้นแต่คุณจะต้องทำ SVD เป็นจำนวนมาก


28
คุณไม่ควรใช้ eig () บนเมทริกซ์ความแปรปรวนร่วมเมื่อคุณสามารถใช้ svd () ได้ ขึ้นอยู่กับจำนวนองค์ประกอบที่คุณวางแผนจะใช้และขนาดของเมทริกซ์ข้อมูลของคุณข้อผิดพลาดเชิงตัวเลขที่นำมาใช้โดยอดีต (มันดำเนินการกับจุดลอยตัวมากกว่า) อาจมีความสำคัญ ด้วยเหตุผลเดียวกันคุณไม่ควรสลับเมทริกซ์อย่างชัดเจนด้วย inv () หากสิ่งที่คุณสนใจจริงๆคือคูณผกผันเวกเตอร์หรือเมทริกซ์ คุณควรใช้การแก้ปัญหา () แทน
dwf

5

คุณไม่จำเป็นต้องมีการสลายตัวของค่าเอกพจน์ (SVD) เต็มรูปแบบในการคำนวณค่าลักษณะเฉพาะและค่าลักษณะเฉพาะทั้งหมดและอาจเป็นข้อห้ามสำหรับเมทริกซ์ขนาดใหญ่ scipyและโมดูลแบบกระจัดกระจายให้ฟังก์ชัน algrebra เชิงเส้นทั่วไปที่ทำงานบนเมทริกซ์แบบเบาบางและหนาแน่นซึ่งมีฟังก์ชันตระกูล eig *:

http://docs.scipy.org/doc/scipy/reference/sparse.linalg.html#matrix-factorizations

Scikit-learnมีการใช้งาน Python PCAซึ่งรองรับเฉพาะเมทริกซ์ที่หนาแน่นในตอนนี้

การกำหนดเวลา:

In [1]: A = np.random.randn(1000, 1000)

In [2]: %timeit scipy.sparse.linalg.eigsh(A)
1 loops, best of 3: 802 ms per loop

In [3]: %timeit np.linalg.svd(A)
1 loops, best of 3: 5.91 s per loop

1
ไม่ใช่การเปรียบเทียบที่ยุติธรรมจริง ๆ เนื่องจากคุณยังต้องคำนวณเมทริกซ์ความแปรปรวนร่วม นอกจากนี้อาจคุ้มค่ากับการใช้สิ่ง linalg ที่เบาบางสำหรับเมทริกซ์ขนาดใหญ่มากเนื่องจากดูเหมือนว่าจะค่อนข้างช้าในการสร้างเมทริกซ์แบบเบาบางจากเมทริกซ์ที่หนาแน่น ตัวอย่างเช่นeigshจริง ๆ แล้วช้ากว่าeighเมทริกซ์แบบไม่เจาะจงประมาณ 4 เท่า เช่นเดียวกับที่เป็นจริงสำหรับเมื่อเทียบกับscipy.sparse.linalg.svds numpy.linalg.svdฉันมักจะใช้ SVD มากกว่าการสลายตัวของค่าลักษณะเฉพาะด้วยเหตุผลที่ @dwf กล่าวถึงและอาจใช้ SVD เวอร์ชันเบาบางหากเมทริกซ์มีขนาดใหญ่มาก
ali_m

2
คุณไม่จำเป็นต้องคำนวณเมทริกซ์แบบเบาบางจากเมทริกซ์ที่หนาแน่น อัลกอริทึมที่มีให้ในโมดูล sparse.linalg อาศัยเฉพาะการดำเนินการคูณเวกเตอร์ matrice ผ่านวิธี matvec ของอ็อบเจ็กต์ตัวดำเนินการ สำหรับเมทริกซ์แบบหนาแน่นนี่ก็เหมือนกับ matvec = dot (A, x) ด้วยเหตุผลเดียวกันคุณไม่จำเป็นต้องคำนวณเมทริกซ์ความแปรปรวนร่วม แต่ให้เฉพาะจุดการดำเนินการ (AT, dot (A, x)) สำหรับ A.
Nicolas Barbey

อ่าตอนนี้ฉันเห็นว่าความเร็วสัมพัทธ์ของวิธีการกระจัดกระจายกับวิธีการไม่แยกส่วนขึ้นอยู่กับขนาดของเมทริกซ์ หากฉันใช้ตัวอย่างของคุณที่เป็น 1000 * 1000 เมทริกซ์แล้วeigshและsvdsจะเร็วกว่าeighและsvdโดยปัจจัยที่ ~ 3 แต่ถ้าเป็นขนาดเล็กบอกว่า 100 * 100 แล้วeighและsvdเร็วจากปัจจัยของ ~ 4 และ ~ 1.5 ตามลำดับ . T จะยังคงใช้ SVD แบบเบาบางในการย่อยสลายค่าลักษณะเฉพาะแบบเบาบาง
ali_m

2
อันที่จริงฉันคิดว่าฉันมีอคติต่อเมทริกซ์ขนาดใหญ่ สำหรับฉันแล้วเมทริกซ์ขนาดใหญ่ก็เหมือนกับ10⁶ * 10⁶มากกว่า 1,000 * 1,000 ในกรณีนี้คุณมักจะไม่สามารถเก็บเมทริกซ์ความแปรปรวนร่วมได้ ...
Nicolas Barbey

4

นี่คือการใช้งานโมดูล PCA สำหรับ python อีกครั้งโดยใช้ numpy, scipy และ C-extensions โมดูลดำเนินการ PCA โดยใช้อัลกอริทึม SVD หรือ NIPALS (Nonlinear Iterative Partial Least Squares) ซึ่งใช้งานใน C.


0

หากคุณกำลังทำงานกับเวกเตอร์ 3 มิติคุณสามารถใช้ SVD รัดกุมโดยใช้แถบเครื่องมือVG มันเป็นเลเยอร์แสงด้านบนของ numpy

import numpy as np
import vg

vg.principal_components(data)

นอกจากนี้ยังมีนามแฝงที่สะดวกหากคุณต้องการเพียงองค์ประกอบหลักแรก:

vg.major_axis(data)

ฉันสร้างไลบรารีเมื่อเริ่มต้นครั้งสุดท้ายซึ่งได้รับแรงบันดาลใจจากการใช้งานเช่นนี้แนวคิดง่ายๆที่มีลักษณะละเอียดหรือทึบใน NumPy

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.