1. 程式人生 > 其它 >【pandas官方文件-使用者指南】3.必要基礎功能

【pandas官方文件-使用者指南】3.必要基礎功能

03-必要基礎功能
   

3.必要的基礎功能

這一節我們討論pandas資料結構的許多常見基本功能。先建立一些示例物件:

  In [16]:
import pandas as pd
import numpy as np
  In [17]:
index = pd.date_range("1/1/2000",periods=8)
s = pd.Series(np.random.randn(5),index=list('abcde'))
df = pd.DataFrame(np.random.randn(8,3), index= index, columns=["A","B","C"])
   

3.1.head()和tail()

  • head()
  • tail()

要檢視Series或DataFrame物件的樣本,用head()和tail()方法。預設顯示元素數量是5個,但可以自定義數量。

  In [18]:
long_series = pd.Series(np.random.randn(1000))
long_series.head()
  Out[18]:
0   -0.290077
1    0.768651
2   -0.224879
3    0.338850
4    2.141244
dtype: float64
  In [19]:
long_series.tail()
  Out[19]:
995    0.200847
996   -1.830154
997   -0.237850
998    1.109558
999   -0.261391
dtype: float64
   

3.2.屬性和底層資料

  • DataFrame/Series.index

  • DataFrame.columns

  • 很多物件.array

  • DataFrame/Series.to_numpy()

Pandas物件有許多允許您訪問元資料的屬性:

  • shape(形狀):給出物件的軸尺寸,與ndarray一致
  • Axis labels(軸標籤)
    • Series:索引(僅軸)
    • DataFrame:索引(行)和列

注意,這些屬性可以安全地分配!

  In [20]:
df[:2]
  Out[20]:  
  A B C
2000-01-01 0.504887 0.365445 -0.969503
2000-01-02 -0.452088 -0.111733 0.428377
  In [21]:
df.columns = [x.lower() for x in df.columns]
df
  Out[21]:  
  a b c
2000-01-01 0.504887 0.365445 -0.969503
2000-01-02 -0.452088 -0.111733 0.428377
2000-01-03 0.146239 -0.478276 0.312021
2000-01-04 -0.335146 -0.616663 -0.917268
2000-01-05 -1.364473 0.190397 0.656422
2000-01-06 0.113224 1.024331 -2.422926
2000-01-07 -0.200932 -0.023491 2.000700
2000-01-08 0.057392 -0.323121 -1.063627
   

pandas物件(Index、Series、DataFrame)可以被認為是陣列的容器,用於儲存資料並進行計算。許多型別的底層陣列是numpy.ndarray。但pandas和第三方庫可以擴充套件NumPy的型別系統(請參閱dtypes)。

要獲得Index或Series中的實際資料,用.array

  In [22]:
s.array
  Out[22]:
<PandasArray>
[  0.6238336771385428,   0.6775427558232908, -0.11953987203281834,
 -0.06043763312601807,  0.48055878291155296]
Length: 5, dtype: float64
  In [23]:
s.index.array # 索引也是array
  Out[23]:
<PandasArray>
['a', 'b', 'c', 'd', 'e']
Length: 5, dtype: object
   

當系列或索引是ExtensionArray時,to_numpy()可能涉及複製資料和強制賦值。參見dtypes。

to_numpy()對生成的numpy.ndarray的d型別提供了一些控制。例如,考慮帶有時區的日期時間。NumPy沒有表示含時區的日期時間dtype,所以有兩種可能有用的表示:

  • object-dtype numpy.ndarray使用Timestamp物件,每個物件使用正確的tz
  • datetime64[ns]-dtype numpy。ndarray,其中的值已轉換為UTC並丟棄了時區

時區可以通過dtype=object儲存

  In [24]:
ser = pd.Series(pd.date_range('2000',periods=2, tz="CET"))
ser.to_numpy(dtype=object)
  Out[24]:
array([Timestamp('2000-01-01 00:00:00+0100', tz='CET'),
       Timestamp('2000-01-02 00:00:00+0100', tz='CET')], dtype=object)
   

或使用dtype='datetime64[ns]'。

  In [25]:
ser.to_numpy(dtype='datetime64[ns]')
  Out[25]:
array(['1999-12-31T23:00:00.000000000', '2000-01-01T23:00:00.000000000'],
      dtype='datetime64[ns]')
   

在DataFrame中獲取“原始資料”可能有點複雜。當你的DataFrame對所有列只有單一的資料型別時,DataFrame.to_numpy()將返回底層資料:

  In [26]:
df.to_numpy()
  Out[26]:
array([[ 0.50488737,  0.36544492, -0.96950252],
       [-0.45208765, -0.11173342,  0.42837726],
       [ 0.14623861, -0.47827639,  0.31202055],
       [-0.33514573, -0.6166628 , -0.91726794],
       [-1.36447295,  0.19039692,  0.65642225],
       [ 0.11322399,  1.0243313 , -2.42292611],
       [-0.20093159, -0.02349061,  2.00070016],
       [ 0.05739156, -0.32312104, -1.06362681]])
   

如果DataFrame包含同構型別的資料,ndarray實際上可以就地修改,並且更改將反映在資料結構中。對於異構資料(例如DataFrame的一些列並不都是相同的dtype),就不會出現這種情況。與軸標籤不同,values屬性本身不能被賦值。

注意:

在處理異構資料時,將選擇生成的ndarray的dtype來容納所有涉及的資料。例如,如果涉及字串,結果將是物件dtype。如果只存在浮點數和整數,則生成的陣列將為浮點dtype。

過去,pandas推薦使用DataFrame/Series.value來從Series或DataFrame中提取資料。仍然可以在舊的程式碼庫和線上程式碼庫中找到這些引用。現置,我們建議避免使用.values,而使用.array.to_numpy().values有以下缺點:

  • 當Series包含擴充套件型別時,不清楚Series.value返回的是NumPy陣列還是擴充套件陣列。Series.array將始終返回一個ExtensionArray,並且永遠不會複製資料。Series.to_numpy()將始終返回NumPy陣列,可能會以複製/強制值為代價。

  • 當你的DataFrame包含混合資料型別時,DataFrame.value可能涉及到複製資料和將值強制轉換成通用dtype,這是一個相對昂貴的操作。DataFrame.to_numpy()更明確,它返回的NumPy陣列可能不是DataFrame中相同資料。

   

3.3.加速操作

pandas支援使用numexpr庫和bottleneck庫來加速某些型別的二進位制數字和布林運算。

這些庫在處理大型資料集時特別有用,並提供加速。numexpr使用智慧分塊、快取和多核。bottleneck是一組專門的cython例程,在處理具有nan的陣列時特別快。

預設情況下,這兩個選項都是啟用的,可以設定選項來控制:

  In [27]:
pd.set_option("compute.use_bottleneck", False)
pd.set_option("compute.use_numexpr", False)
   

3.4.靈活的二元運算

對於pandas資料結構之間的二元運算,有兩個關鍵點值得關注:

  • 較高(如DataFrame)和較低維(如Series)物件之間的廣播行為(Broadcasting behavior,一直不知道怎麼翻譯這個詞)。

  • 計算中的缺失值。

   

3.4.1.匹配/廣播行為

DataFrame有add()、sub()、mul()、div()和相關函式radd()、rsub()、…用於執行二進位制運算(這些個函式太多,參見API文件)。對於廣播行為,Series的輸入是主要關注點。使用這些函式,可以通過axis關鍵字使用來匹配索引或列:

  In [28]:
df = pd.DataFrame(
    {
        "one": pd.Series(np.random.randn(3), index=["a", "b", "c"]),
        "two": pd.Series(np.random.randn(4), index=["a", "b", "c", "d"]),
        "three": pd.Series(np.random.randn(3), index=["b", "c", "d"]),
    }
)
df
  Out[28]:  
  one two three
a 1.139565 -0.565203 NaN
b -1.184934 1.543159 0.128045
c -1.633771 0.593862 -0.797587
d NaN -0.521046 -0.335744
  In [29]:
row = df.iloc[1] # 第2行
column = df['two'] # 第“two”列
df.sub(row, axis='columns')  # df.sub(row, axis=1) 等效
  Out[29]:  
  one two three
a 2.324499 -2.108362 NaN
b 0.000000 0.000000 0.000000
c -0.448837 -0.949297 -0.925631
d NaN -2.064204 -0.463789
  In [30]:
df.sub(column,axis=0) # df.sub(column, axis="index") 等效
  Out[30]:  
  one two three
a 1.704769 0.0 NaN
b -2.728092 0.0 -1.415114
c -2.227633 0.0 -1.391449
d NaN 0.0 0.185302
   

此外,還可以將多層索引的DataFrame的一個層級與Series對齊。

  In [31]:
dfmi = df.copy()
dfmi.index = pd.MultiIndex.from_tuples(
    [(1,'a'),(1,'b'),(1,'c'),(2,'a')],names=['first','second']
)
dfmi.sub(column,axis=0,level='second')
  Out[31]:  
    one two three
first second      
1 a 1.704769 0.000000 NaN
b -2.728092 0.000000 -1.415114
c -2.227633 0.000000 -1.391449
2 a NaN 0.044158 0.229459
   

Series和Index也支援內建函式divmod()。此函式同時向下除和取模操作,返回一個與左邊相同型別的二元元組。例如:

  In [32]:
s = pd.Series(np.arange(10))
s
  Out[32]:
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int32
  In [33]:
divmod(s, 3) # 結果是(div,rem)的元組,div是s/3的整數結果,rem是餘數
  Out[33]:
(0    0
 1    0
 2    0
 3    1
 4    1
 5    1
 6    2
 7    2
 8    2
 9    3
 dtype: int32,
 0    0
 1    1
 2    2
 3    0
 4    1
 5    2
 6    0
 7    1
 8    2
 9    0
 dtype: int32)
  In [34]:
idx = pd.Index(np.arange(10))
idx
  Out[34]:
Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype='int64')
  In [35]:
divmod(idx, 3)
  Out[35]:
(Int64Index([0, 0, 0, 1, 1, 1, 2, 2, 2, 3], dtype='int64'),
 Int64Index([0, 1, 2, 0, 1, 2, 0, 1, 2, 0], dtype='int64'))
   

不同元素的除法取模計算;

  In [36]:
div, rem = divmod(s, [2, 2, 3, 3, 4, 4, 5, 5, 6, 6])
div, rem
  Out[36]:
(0    0
 1    0
 2    0
 3    1
 4    1
 5    1
 6    1
 7    1
 8    1
 9    1
 dtype: int32,
 0    0
 1    1
 2    2
 3    0
 4    0
 5    1
 6    1
 7    2
 8    2
 9    3
 dtype: int32)
   

3.4.2.缺失值/值填充操作

在Series和DataFrame中,算術函式可以選擇輸入fill_value,即在一個位置最多缺少一個值時替換。例如,當加兩個DataFrame物件時,將NaN視為0,除非兩個DataFrame都缺少該值,在這種情況下,結果將是NaN(可以使用fillna將NaN替換為其他值)。

  In [37]:
df
  Out[37]:  
  one two three
a 1.139565 -0.565203 NaN
b -1.184934 1.543159 0.128045
c -1.633771 0.593862 -0.797587
d NaN -0.521046 -0.335744
  In [38]:
df2 = df.copy()
df2.loc['a','three'] = 1.0 # df和df2的區別在 第a行第three列
df2
  Out[38]:  
  one two three
a 1.139565 -0.565203 1.000000
b -1.184934 1.543159 0.128045
c -1.633771 0.593862 -0.797587
d NaN -0.521046 -0.335744
  In [39]:
df + df2 # a-three:NaN+1 = NaN, d-one:NaN+NaN = NaN
  Out[39]:  
  one two three
a 2.279130 -1.130407 NaN
b -2.369867 3.086317 0.256089
c -3.267541 1.187724 -1.595173
d NaN -1.042091 -0.671488
  In [40]:
df.add(df2,fill_value=0) # a-three:0+1 = 1, d-one:0+NaN = NaN
  Out[40]:  
  one two three
a 2.279130 -1.130407 1.000000
b -2.369867 3.086317 0.256089
c -3.267541 1.187724 -1.595173
d NaN -1.042091 -0.671488
   

3.4.3.靈活比較

Series和DataFrame的二進位制比較方法eq, ne, lt, gt, le和ge的廣播行為類似於上面的二進位制算術: | 方法 | 英文 | 中文 | |---|---|---| |eq|equal to|等於| |ne|not equal to |不等於| |lt|less than|小於| |gt|greater than|大於| |le|less than or equal to|小等於| |ge|greater than or equal to|大等於|

  In [41]:
df.gt(df2)
  Out[41]:  
  one two three
a False False False
b False False False
c False False False
d False False False
  In [42]:
df2.ne(df)
  Out[42]:  
  one two three
a False False True
b False False False
c False False False
d True False False
   

3.4.4.布林簡化

empty、any()、all()和bool()提供了彙總布林結果的方法。

  In [43]:
(df > 0).all() , (df > 0).any().any() # 可以彙總至最後一層
  Out[43]:
(one      False
 two      False
 three    False
 dtype: bool,
 True)
   

empty屬性判斷pandas物件是否為空。

  In [44]:
df.empty, pd.DataFrame(columns=list('abc')).empty
  Out[44]:
(False, True)
   

要在布林上下文中計算單元素pandas物件,用bool():

  In [45]:
pd.Series([True]).bool() , pd.Series([False]).bool()
  Out[45]:
(True, False)
  In [46]:
pd.DataFrame([[True]]).bool(), pd.DataFrame([[False]]).bool()
  Out[46]:
(True, False)
   

DataFrame和Series物件不能直接參與布林運算,否則報錯。

  In [47]:
if df:
    pass
"""
ValueError: The truth value of a DataFrame is ambiguous. 
Use a.empty, a.bool(), a.item(), a.any() or a.all().
"""
   
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
e:\NOTES\pandas-note\scripts\使用者指南\03-必要基礎功能.ipynb Cell 52' in <cell line: 1>()
----> <a href='vscode-notebook-cell:/e%3A/NOTES/pandas-note/scripts/%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97/03-%E5%BF%85%E8%A6%81%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD.ipynb#ch0000051?line=0'>1</a> if df:
      <a href='vscode-notebook-cell:/e%3A/NOTES/pandas-note/scripts/%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97/03-%E5%BF%85%E8%A6%81%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD.ipynb#ch0000051?line=1'>2</a>     pass
      <a href='vscode-notebook-cell:/e%3A/NOTES/pandas-note/scripts/%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97/03-%E5%BF%85%E8%A6%81%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD.ipynb#ch0000051?line=2'>3</a> """
      <a href='vscode-notebook-cell:/e%3A/NOTES/pandas-note/scripts/%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97/03-%E5%BF%85%E8%A6%81%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD.ipynb#ch0000051?line=3'>4</a> ValueError: The truth value of a DataFrame is ambiguous. 
      <a href='vscode-notebook-cell:/e%3A/NOTES/pandas-note/scripts/%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97/03-%E5%BF%85%E8%A6%81%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD.ipynb#ch0000051?line=4'>5</a> Use a.empty, a.bool(), a.item(), a.any() or a.all().
      <a href='vscode-notebook-cell:/e%3A/NOTES/pandas-note/scripts/%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97/03-%E5%BF%85%E8%A6%81%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD.ipynb#ch0000051?line=5'>6</a> """

File e:\NOTES\venv\lib\site-packages\pandas\core\generic.py:1527, in NDFrame.__nonzero__(self)
   <a href='file:///e%3A/NOTES/venv/lib/site-packages/pandas/core/generic.py?line=1524'>1525</a> @final
   <a href='file:///e%3A/NOTES/venv/lib/site-packages/pandas/core/generic.py?line=1525'>1526</a> def __nonzero__(self):
-> <a href='file:///e%3A/NOTES/venv/lib/site-packages/pandas/core/generic.py?line=1526'>1527</a>     raise ValueError(
   <a href='file:///e%3A/NOTES/venv/lib/site-packages/pandas/core/generic.py?line=1527'>1528</a>         f"The truth value of a {type(self).__name__} is ambiguous. "
   <a href='file:///e%3A/NOTES/venv/lib/site-packages/pandas/core/generic.py?line=1528'>1529</a>         "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
   <a href='file:///e%3A/NOTES/venv/lib/site-packages/pandas/core/generic.py?line=1529'>1530</a>     )

ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
  In [ ]:
df and df2
"""
ValueError: The truth value of a DataFrame is ambiguous. 
Use a.empty, a.bool(), a.item(), a.any() or a.all().
"""
   

3.4.5.判斷物件相等

equals()用來比較兩個物件是否相等。而不能用布林運算(如all()方法)。

  • 布林運算中,NaN直接認定為False
  • 需要注意,資料必須在順序上也要保持一致。
  In [ ]:
df + df == df *2
  Out[ ]:  
  one two three
a True True False
b True True True
c True True True
d False True True
  In [ ]:
(df+df==df*2).all()
  Out[ ]:
one      False
two       True
three    False
dtype: bool
  In [ ]:
(df + df).equals(df *2)
  Out[ ]:
True
  In [ ]:
df1 = pd.DataFrame({'col': ['foo',0,np.nan]})
df2 = pd.DataFrame({"col": [np.nan, 0, "foo"]}, index=[2, 1, 0])
df1.equals(df2)
  Out[ ]:
False
  In [ ]:
df1.equals(df2.sort_index()) # 需要保證順序也一樣
  Out[ ]:
True
   

3.4.6.比較類陣列物件

  • 資料結構與標量值進行比較:
  In [ ]:
pd.Series(["foo","bar","baz"])=='foo'
  Out[ ]:
0     True
1    False
2    False
dtype: bool
  In [ ]:
pd.Index(["foo","bar","baz"])=="foo"
  Out[ ]:
array([ True, False, False])
   
  • 與相同長度的不同類似陣列的物件之間的元素比較:
  In [ ]:
pd.Series(["foo","bar","baz"]) == pd.Index(["foo", "bar", "qux"])
  Out[ ]:
0     True
1     True
2    False
dtype: bool
  In [ ]:
pd.Series(["foo", "bar", "baz"]) == np.array(["foo", "bar", "qux"])
  Out[ ]:
0     True
1     True
2    False
dtype: bool
   

比較不同長度的Index或Series物件會報錯,但在NumPy中,是可以廣播比較的,不同會返回False。

  In [ ]:
np.array([1, 2, 3]) == np.array([1, 2])
   
C:\Users\watalo\AppData\Local\Temp\ipykernel_11620\1336612208.py:1: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
  np.array([1, 2, 3]) == np.array([1, 2])
Out[ ]:
False
   

3.4.7.組合重疊的資料集

組合兩個DataFamre物件,其中一個DataFamre中缺少的值有條件地用另一個DataFamre中類似標記的值填充。實現該操作的函式是combine_first(),我們舉例說明如下:

    In [ ]:
df1 = pd.DataFrame(
    {"A": [1.0, np.nan, 3.0, 5.0, np.nan], "B": [np.nan, 2.0, 3.0, np.nan, 6.0]}
)

df2 = pd.DataFrame(
    {
        "A": [5.0, 2.0, 4.0, np.nan, 3.0, 7.0],
        "B": [np.nan, np.nan, 3.0, 4.0, 6.0, 8.0],
    }
)

df1, df2
  Out[ ]:
(     A    B
 0  1.0  NaN
 1  NaN  2.0
 2  3.0  3.0
 3  5.0  NaN
 4  NaN  6.0,
      A    B
 0  5.0  NaN
 1  2.0  NaN
 2  4.0  3.0
 3  NaN  4.0
 4  3.0  6.0
 5  7.0  8.0)
  In [ ]:
df1.combine_first(df2)
  Out[ ]:  
  A B
0 1.0 NaN
1 2.0 2.0
2 3.0 3.0
3 5.0 4.0
4 3.0 6.0
5 7.0 8.0
   

3.4.8.通用DataFrame組合

combine_first()比combine()更常用。此方法採用另一個DataFrame和一個組合器函式,對齊輸入DataFrame,然後傳遞系列的組合器函式對(即名稱相同的列)。

例如,要重現上面的combine_first():

  In [ ]:
def combiner(x, y):
    return np.where(pd.isna(x),y,x)

df1.combine(df2,combiner)
  Out[ ]:  
  A B
0 1.0 NaN
1 2.0 2.0
2 3.0 3.0
3 5.0 4.0
4 3.0 6.0
5 7.0 8.0
   

3.5.描述統計學

有很多方法來計算描述性統計和對Series和DataFrame的其他相關操作。其中大部分是聚合(因此產生較低維的結果),如sum()、mean()和quantile(),但也有一些產生相同大小的物件,如cumsum()和cumprod()。

一般來說,這些方法接受軸引數,就像ndarray.{sum,std,…}一樣,但軸可以由名稱或整數指定:

  • Series:不需要軸引數
  • DataFrame:“索引”(軸=0,預設),“列”(軸=1)

例如:

  In [48]:
df
  Out[48]:  
  one two three
a 1.139565 -0.565203 NaN
b -1.184934 1.543159 0.128045
c -1.633771 0.593862 -0.797587
d NaN -0.521046 -0.335744
  In [51]:
df.mean(0), df.mean(1)
  Out[51]:
(one     -0.559713
 two      0.262693
 three   -0.335095
 dtype: float64,
 a    0.287181
 b    0.162090
 c   -0.612498
 d   -0.428395
 dtype: float64)
   

所有這些方法都有一個skipna選項,指示是否排除缺失值(預設情況下為True):

  In [52]:
df.sum(0, skipna=False)
  Out[52]:
one           NaN
two      1.050772
three         NaN
dtype: float64
  In [53]:
df.sum(axis=1, skipna=True)
  Out[53]:
a    0.574362
b    0.486270
c   -1.837495
d   -0.856790
dtype: float64
   

結合廣播/算術行為,可以非常簡潔地描述各種統計過程,如標準化(呈現資料的零均值和標準差為1):

  In [54]:
ts_stand = (df - df.mean()) / df.std()
ts_stand.std()
  Out[54]:
one      1.0
two      1.0
three    1.0
dtype: float64
  In [56]:
xs_stand = df.sub(df.mean(1), axis=0).div(df.std(1), axis=0)
xs_stand.std(1)
  Out[56]:
a    1.0
b    1.0
c    1.0
d    1.0
dtype: float64
   

注意,像cumsum()和cumprod()方法保留了NaN值的位置。這與expanding()和rolling()不同,因為NaN行為還由min_periods引數決定。

  In [57]:
df.cumsum()
  Out[57]:  
  one two three
a 1.139565 -0.565203 NaN
b -0.045368 0.977955 0.128045
c -1.679139 1.571817 -0.669542
d NaN 1.050772 -1.005286
   

下面是常用函式的快速參考彙總表。每個物件還帶有一個可選的level引數,該引數僅在物件具有分層索引時適用。

   
Function Description 功能
count Number of non-NA observations 非缺失值計數
sum Sum of values 求和
mean Mean of values 求平均值
mad Mean absolute deviation 平均絕對偏差
median Arithmetic median of values 值的算術中位數
min Minimum 最小值
max Maximum 最大值
mode Mode
abs Absolute Value 絕對值
prod Product of values  
std Bessel-corrected sample standard deviation 標準差
var Unbiased variance 無偏方差
sem Standard error of the mean 平均標準誤差
skew Sample skewness (3rd moment) 樣本偏度
kurt Sample kurtosis (4th moment) 樣本峰度
quantile Sample quantile (value at %) 樣本分位數
cumsum Cumulative sum 累計總和
cumprod Cumulative product  
cummax Cumulative maximum 累積最大值
cummin Cumulative minimum 累積最小值