指向数据的两种方式:按标签和按位置
DataFrame 有两种寻址方式,把它们搞混是 pandas 最常见的 bug。.loc 按标签选择:行用索引中的标签,列用列名。.iloc 按整数位置选择,从 0 开始计数,和列表、NumPy 数组一样。两者都接收 [行, 列]:df.loc["AAPL", "price"] 是一个值,df.iloc[0, 0] 是第一行第一列的值。省略第二部分表示取整行:df.loc["AAPL"] 是 AAPL 这一行(Series)。
import pandas as pd
df = pd.DataFrame(
{"price": [184.25, 410.2, 171.0, 251.0],
"volume": [200, 50, 300, 120],
"sector": ["tech", "tech", "tech", "auto"]},
index=["AAPL", "MSFT", "GOOG", "TSLA"],
)
print(df.loc["AAPL", "price"], df.iloc[0, 0]) # 184.25 184.25 same cell, two addresses
print(df.loc["TSLA"].to_dict()) # {'price': 251.0, 'volume': 120, 'sector': 'auto'}
print(df.iloc[-1].to_dict()) # the same row: position -1 is the last
print(df.loc[["TSLA", "AAPL"], "price"]) # a list of labels picks in that order
# TSLA 251.00
# AAPL 184.25
# Name: price, dtype: float64
try:
df.loc["XXX", "price"] # a label that does not exist
except KeyError as error:
print("KeyError", error) # KeyError 'XXX'切片:标签切片包含终点
两种方式下切片的行为不同。位置切片 df.iloc[1:3] 遵循 Python 的规则,不含终点:即位置 1 和 2。标签切片 df.loc["MSFT":"TSLA"] 包含两端的标签,因为标签没有天然的“下一个”。标签切片遵循索引当前的顺序,索引不必已排序。对于列,传入列表(["price", "volume"])即可按给定顺序选取。忘了自己用的是哪种方式,会在没有任何报错的情况下得到偏差一位的结果。
import pandas as pd
df = pd.DataFrame(
{"price": [184.25, 410.2, 171.0, 251.0], "volume": [200, 50, 300, 120]},
index=["AAPL", "MSFT", "GOOG", "TSLA"],
)
print(df.iloc[1:3]) # positions 1 and 2: the end is excluded
# price volume
# MSFT 410.2 50
# GOOG 171.0 300
print(df.loc["MSFT":"TSLA", ["volume", "price"]]) # labels MSFT..TSLA: the end is included
# volume price
# MSFT 50 410.2
# GOOG 300 171.0
# TSLA 120 251.0
print(df.iloc[:, 0].tolist()) # the first column, by position
# [184.25, 410.2, 171.0, 251.0]用 loc 做条件选择与赋值
.loc 的行部分还可以接收布尔掩码:df.loc[df["volume"] > 100, "price"] 一步就得到满足条件的行的价格列。.loc 也是对选定部分赋值的方式:df.loc["MSFT", "price"] = 1.0 修改一个单元格,df.loc[df["volume"] < 100, "volume"] = 0 修改所有符合条件的行。避免 df[df["volume"] < 100]["volume"] = 0 这样的链式写法:它作用在一个临时副本上。在 pandas 3 中,派生出的表绝不会改动原表,所以要在函数里修改表,应先 df.copy(),用 .loc 赋值,再返回该副本。
import pandas as pd
df = pd.DataFrame(
{"price": [184.25, 410.2, 171.0, 251.0], "volume": [200, 50, 300, 120]},
index=["AAPL", "MSFT", "GOOG", "TSLA"],
)
print(df.loc[df["volume"] > 100, "price"]) # a Boolean mask for rows, a label for the column
# AAPL 184.25
# GOOG 171.00
# TSLA 251.00
# Name: price, dtype: float64
def with_price(frame, symbol, price):
out = frame.copy() # never edit the caller's table
out.loc[symbol, "price"] = price
return out
changed = with_price(df, "MSFT", 1.0)
print(changed.loc["MSFT", "price"], df.loc["MSFT", "price"]) # 1.0 410.2
fixed = df.copy()
fixed.loc[fixed["volume"] < 100, "volume"] = 0 # assign to all matching rows at once
print(fixed["volume"].tolist()) # [200, 0, 300, 120]import pandas as pd
# Integer labels are labels, not positions: a classic trap
d = pd.DataFrame({"x": [1.0, 2.0, 3.0]}, index=[10, 20, 30])
print(d.loc[20, "x"], d.iloc[0, 0]) # 2.0 1.0 label 20 is the second row
print(d.loc[10:20]["x"].tolist()) # [1.0, 2.0] labels 10 through 20, end included
print(d.iloc[0:2]["x"].tolist()) # [1.0, 2.0] positions 0 and 1, end excluded
try:
d.loc[0, "x"] # there is no label 0, even though position 0 exists
except KeyError as error:
print("KeyError", error) # KeyError 0.loc,只有位置本身就是重点时(“最后一行”“前三行”)才用 .iloc。基于标签的代码在行被重新排序后依然有效;基于位置的代码则会悄悄开始选到别的行。单独的 df[...] 是有多种含义的简写(列、列列表、行掩码),所以在重要的函数里要明确写出 .loc 或 .iloc。实验有意要求使用其中之一。df.loc[行标签, 列标签]按标签;df.iloc[行位置, 列位置]按从 0 开始的位置。iloc切片不含终点;loc标签切片包含终点;缺失的标签会引发KeyError。df.loc[mask, "col"]筛选并选取;在.copy()上用.loc赋值并返回。
下一节:groupby 与聚合,把表格分成若干组并分别汇总。
实验:评分内容
- 用
price_of按代码查一个价格25 分 - 用
label_range取两个代码之间的行25 分 - 用
position_range按位置取行25 分 - 用
set_price在副本中修正一个价格25 分