楔子

pandas是一个很强大的库,但是在使用的过程中难免会遇见各种奇葩的异常,而这些异常却又很难让人定位到底是哪一步出了问题。下面就来看看pandas中的一些令人感到费解的异常吧,看看你有没有遇到过,如果没有的话,那么说明你pandas可能用的不够多哦。

ヽ( ̄ω ̄( ̄ω ̄〃)ゝ一起来看看

1. SettingWithCopyWarning:

当然这不是个异常,而是一个警告,这个警告相信大多数人都遇到过,尤其是初学pandas的时候。这个警告具体内容如下:

SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/......

我们来复现一下这个警告:

import pandas as pd

df = pd.DataFrame({"name": ["mashiro", "koishi", "satori", "kurisu"],
                   "age": [17, 16, 17, 19],
                   "adult": [None, None, None, None]})

print(df)
"""
      name  age adult
0  mashiro   17  None
1   koishi   16  None
2   satori   17  None
3   kurisu   19  None
"""
# 我现在想将df中age > 18对应的audit设置为True

# 但是会发现没有效果,并且SettingWithCopyWarning就是由这一行代码引发的
df[df["age"] > 16]["adult"] = True
print(df)
"""
      name  age adult
0  mashiro   17  None
1   koishi   16  None
2   satori   17  None
3   kurisu   19  None
"""

为什么会出现这个原因呢?因为df[df["age"] > 16]得到的是原始DataFrame的一份拷贝,因此其相应的操作不会影响原来的DataFrame。尽管这样的操作是允许的,但是却无法得到正确的结果,因此pandas弹出了一个警告。

# 真正的做法是使用loc或者iloc
df.loc[df["age"] > 18, "adult"] = True
print(df)
"""
      name  age adult
0  mashiro   17  None
1   koishi   16  None
2   satori   17  None
3   kurisu   19  None
"""

2. TypeError: ‘Series’ objects are mutable, thus they cannot be hashed

这个异常实际上比较常见了,说白了就是你不小心把loc或者iloc给丢掉了,我们还用上面的例子

try:
    df[df["age"] > 18, "adult"] = True
except Exception as e:
    print(e)  # 'Series' objects are mutable, thus they cannot be hashed

3. ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all()

我们知道,像if a:,not a之类的,本质上都是调用a的__bool__方法,可以理解为bool(a)

class A:

    def __bool__(self):
        return True


if A():
    print("123")
else:
    print("456")
print(bool(A()))
"""
123
True
"""


# 由于A的__bool__返回了True, 所以bool(A())为True
# 我们将其改为False
A.__bool__ = lambda self: False
print(bool(A()))  # False

但是对于一个Series或者numpy中的array不可以这么做。

import pandas as pd

s = pd.Series([True, False])
try:
    bool(s)
except Exception as e:
    print(e)  # The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

try:
    if s.values:  # 得到一个numpy中的array
        pass
except Exception as e:
    print(e)  # The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()


"""
对于一个Series对象来说,不可以使用其布尔值,应当使用 s.all()、s.any()或者np.all(s)、np.any(s)

但是对于numpy中的array来讲,如果这个array里面的元素不止一个,那么也不可以使用其布尔值,但如果该array中只有一个元素的话是个例外
此时:if np.array([123]) 等价于 if 123

因此如果使用其布尔值的话,最好使用np.all()或者np.any()将一个序列变成单个布尔值
"""

# 但如果只是希望像列表一样,如果该Series对象里面有值就是真,否则就是假
# 那么建议通过 if len(s):这种方式来判断
# 同理DataFrame也是如此

4. 布尔你咋啦?

我们知道,可以对两个类型为bool的Series对象进行 与、或、非 等操作,但是结果真的一定是我们想要的吗?

import pandas as pd

s1 = pd.Series([True, True, True])
s2 = pd.Series([True, False, True], index=[1, 2, 3])

print(s1 & s2)
"""
0    False
1     True
2    False
3    False
dtype: bool
"""
# 我们看到与运算之后,长度变成了4,究其原因就是两个Series索引不同造成的
# 而Series的很多操作都是基于索引进行对齐的,并不是简简单单地按照顺序
# 但如果对应的索引一样的话,那么也可以认为就是按照顺序从上到下
# 但如果索引不一样的话,pandas是怎么做的呢?答案是使用reindex

# 首先找到两个Series对象中出现的所有不重复索引
index = s1.index | s2.index
print(index)  # Int64Index([0, 1, 2, 3], dtype='int64')

# 使用reindex进行对齐, 不存在的使用NaN代替,当然我们也可以指定fill_value进行填充
# 比如fill_value=False
print(s1.reindex(index))
"""
0    True
1    True
2    True
3     NaN
dtype: object
"""
print(s2.reindex(index))
"""
0      NaN
1     True
2    False
3     True
dtype: object
"""

# 所以s1 & s2最终等价于 s1.reindex(index) & s2.reindex(index)
# 因此即使两者个数不同也是没有问题的
s1 = pd.Series([True, True, True, True])
s2 = pd.Series([True, True, True], index=[1, 2, 3])
print(s1 & s2)
"""
0    False
1     True
2     True
3     True
dtype: bool
"""

总之pandas中很多操作,并不是我们想的那么简单,pandas的Series和DataFrame都具备索引的概念,通过索引来定位速度是非常快的。但是不注意就会造成陷阱,究其原因就是很多操作在定位的时候是基于索引来定位的,并不是简单的按照顺序。比如:s1 & s2,指的是s1和s2中相同索引对应的元素进行与运算,当然如果有对应不上的,事先已经通过reindex处理好了。

当然,如果我们不希望考虑索引的话,只是单纯的希望按照顺序进行位运算,该怎么做呢?办法有两种

import pandas as pd

s1 = pd.Series([True, True, True])
s2 = pd.Series([True, False, True], index=[1, 2, 3])

# 对Series使用reset_index即可,当然要指定drop=True,否则就变成DataFrame了
# 一旦reset_index之后两者索引从头到尾就是一致的了
print(s1.reset_index(drop=True) & s2.reset_index(drop=True))
"""
0     True
1    False
2     True
dtype: bool
"""

# 或者转成numpy中的array
# 我们知道Series等价于numpy中的n个array,分别存放索引、值等等
# 我们调用s.index即可拿到索引,s.values即可拿到值
print(s1.values & s2)
"""
1     True
2    False
3     True
dtype: bool
"""
# 如果其中是一个array的话,那么它没有索引的概念,索引此时也是单纯的一个一个对应进行运算
# 当然得到的结果也是一个Series,索引和运算的Series的索引保持一致

# 或者都转成array
print(s1.values & s2.values)  # [ True False  True]

# 但是注意:如果其中一方转成了array,那么此时就要求两个序列的布尔元素个数是必须相等的
# 此时就不会再通过reindex进行扩展了,因为array没有reindex
# 当然都转成array就更不用说了

我们说了很多关于索引的话题,之所以强调这一点,是因为这里面存在一个坑点。我们知道对于DataFrame对象来说,通过df[xxx]可以取得相应的数据,xxx的身份不同,取得的数据也不同

如果xxx是一个标量, 那么df[xxx]表示获取df的某一列,得到一个Series对象
如果xxx是一个列表或者numpy的narray, 那么xxx里面可以是该DataFrame对象的列名,表示获取指定的多个列,得到DataFrame对象
如果xxx是一个列表或者numpy的narray,那么这个xxx里面还可以是布尔值,并且其长度要和该DataFrame对象的行数相等,表示获取对应的行数。对应为True的保留,为False的不要,也是得到DataFrame对象

举个栗子

import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})


# 只有第一个为True,因此被保留了下来
print(df[[True, False, False]])
"""
   a   b
0  1  11
"""

try:
    # 但此时指定的4个布尔值
    print(df[[True, False, False, False]])
except Exception as e:
    print(e)  # Item wrong length 4 instead of 3.
"""
告诉我们个数不匹配
所以上面之所以说了索引,就是因为在做运算的的时候可能导致布尔值的个数最终和DataFrame的行数不匹配
从而在筛选指定记录的时候发生报错
"""

如果是Series也是可以的

import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})

flag = pd.Series([True, False, False])
print(df[flag])
"""
   a   b
0  1  11
"""
# 当然里面也可以是一个Series,当然这个Series不仅个数要匹配,索引也要匹配
# 我们知道df的索引是 0 1 2,那么该flag的索引也必须是0 1 2(但是顺序不要求)
flag.index = [1, 2, 3]
try:
    df[flag]
except Exception as e:
    print(e)
# Unalignable boolean Series provided as indexer (index of the boolean Series and of the indexed object do not match).


"""
我们看到报了上面那个错误,意思我们传递了bool类型的Series对象,但是其索引和DataFrame的索引不匹配
"""

# 我们再改一下
flag.index = [1, 2, 0]
print(flag)
"""
1     True
2    False
0    False
dtype: bool
"""
print(df[flag])
"""
   a   b
1  2  22
"""
# 我们看到此时布尔值True对应的索引为1,那么筛选的就不再是df中的第一行了
# 而是索引为1的行,也就是第二行。
# 因此尽管对Series的索引的值有要求,但是对顺序却并没有要求
# 所以这种情况下,筛选出来的数据可能就和我们想象的不一样,明明第一个是True,为啥却把DataFrame的第二行选出来了
# 原因就是,虽然第一个是True,但是它对应的索引是1

因此索引这个东西在定位数据的时候,会非常方便,因为我们可以直接通过索引去定位。但是在一些操作方面,我们关心的并不是它的索引,而是它的值,比如:s1 & s2,或者df[flag],这个时候我们只是对内部的布尔值感兴趣,那么直接把s1、s2、flag这些变成numpy中的array之后,再去传递即可。此时就无需考虑索引啥的了。

5. 怎么给DataFrame添加字段呢?

给一个DataFrame添加一个字段,并附上初始值有以下几种方式。

import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})

# 可以给一个标量,然后会自动进行广播
df["c"] = "xx"

# 也可以是一个列表,如果里面只有一个元素,那么和标量是等价的
df["d"] = "yy"
print(df)
"""
   a   b   c   d
0  1  11  xx  yy
1  2  22  xx  yy
2  3  33  xx  yy
"""

# 如果是列表里面有多个值,那么个数必须和df的行数匹配
# 否则会报出ValueError: Length of values does not match length of index
df["e"] = ["x", "y", "z"]
print(df)
"""
   a   b   c   d  e
0  1  11  xx  yy  x
1  2  22  xx  yy  y
2  3  33  xx  yy  z
"""

# 还有一种办法是通过df.assign,这种办法可以同时创建多个列
df = df[["a", "b"]]
df = df.assign(
    # 这里指定接收一个参数的函数,这个参数就是整个df
    # 通过关键字参数,那么参数名就是列名
    c=lambda x: x["a"] + 1,
    d=lambda x: x["b"] * 2,
    e=lambda x: ["i", "j", "k"],
    f=lambda x: "哼哼"
)
print(df)
"""
   a   b  c   d  e   f
0  1  11  2  22  i  哼哼
1  2  22  3  44  j  哼哼
2  3  33  4  66  k  哼哼
"""

给一个DataFrame添加一个字段,同样存在索引的陷阱

import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})

df["c"] = pd.Series(["x", "y", "z"], index=[0, 2, 3])
print(df)
"""
   a   b    c
0  1  11    x
1  2  22  NaN
2  3  33    y
"""

原因无需我再多解释,总而言之就是我们刚才说的那样,如果我们只关心值,不关心索引,那么就不要传递Series对象,直接传递numpy中的array或者列表即可,这样我们就根本不需要考虑索引对齐的问题。

传递一个一维序列是可以的,那么传递一个DataFrame对象会如何呢?

import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})
df1 = pd.DataFrame({"x": ["aa", 22, None], "y": [">>", "^^", "YY"]})

try:
    # 因为df1有两个字段,这里我们只指定了一个
    df["c"] = df1
except Exception as e:
    print(e)  # Wrong number of items passed 2, placement implies 1

# 我们知道df1["x"]是个Series,所以df["c"] = df1["x"]肯定没有错
# 但是df["c"] = df1[["x"]]呢?  df1[["x"]]显然是个DataFrame
df["c"] = df1[["x"]]
print(df)
"""
   a   b     c
0  1  11    aa
1  2  22    22
2  3  33  None
"""
# 可以看到,如果DataFrame只有一个字段,那么等价于Series


# 最后,df["xx"] = xx 这种方式, 在xx是一维序列的前提下 完全等价于 df.loc[:, "xx"] = xx
# 但如果xx是一个DataFrame的话就不一样了
df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})
df1 = pd.DataFrame({"x": ["aa", 22, None], "y": [">>", "^^", "YY"]})

df.loc[:, "c"] = df1
print(df)
"""
   a   b   c
0  1  11 NaN
1  2  22 NaN
2  3  33 NaN
"""
# 我们惊奇地发现它居然没有报错,但结果却是NaN
# 我们rename一下
df1 = df1.rename(columns={"y": "c"})
df.loc[:, "c"] = df1
print(df)
"""
   a   b   c
0  1  11  >>
1  2  22  ^^
2  3  33  YY
"""
# 因此我们发现在使用df.loc[:, "xx"] = df1的时候
# 会自动去找df1中列名为"xx"的列,如果找不到就为NaN

如果给DataFrame添加多个字段的话,除了assign之外,还有什么办法呢?

import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})
df1 = pd.DataFrame({"x": ["aa", 22, None], "y": [">>", "^^", "YY"]})

df[["c", "d"]] = df1
print(df)
"""
   a   b     c   d
0  1  11    aa  >>
1  2  22    22  ^^
2  3  33  None  YY
"""
# 这里可以要求列名不一致,但是个数必须要匹配


df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})
df1 = pd.DataFrame({"x": ["aa", 22, None], "y": [">>", "^^", "YY"]})
# 但是对于loc来说,无法添加多个字段
# 添加一个字段是可以的,但是多个不行
try:
    df.loc[:, ["c", "d"]] = df1
except Exception as e:
    # loc表示筛选,而df的列中没有"c"和"d"
    # 即使是df.loc[:, ["c"]]也不可以,但是df.loc[:, "c"]是可以的
    print(e)  # "None of [Index(['c', 'd'], dtype='object')] are in the [columns]"

# 所以df[["c", "d"]] = df1,如果列c、d不存在, 那么会自动添加
# 但是对于df.loc[:, ["c", "d"]] = df1,如果c、d不存在,则报错,注意:不是都不存在,而是只要有一个不存在就报错
# 如果是指定了不存在的索引,暂时不会报错,而是弹出一个警告
print(df.loc[[1, 11]])
"""
      a     b
1   2.0  22.0
11  NaN   NaN
"""
# 我们看到指定了不存在的索引,那么自动为NaN
# 但同时会抛出一个FutureWarning:
"""
Passing list-likes to .loc or [] with any missing label will raise
KeyError in the future, you can use .reindex() as an alternative.
"""
# 意思是让我们先reindex一下

所以如果想添加多个字段,可以直接通过df[["c1", "c2"]] = df1的方式,但是注意:右边写的df1,所以右边需要也是一个DataFrame,并且两者列数相等

import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3], "b": [11, 22, 33]})
df1 = pd.DataFrame({"x": ["aa", 22, None], "y": [">>", "^^", "YY"]})

# 这里我们本意是想创建两个字段,值都为None
# 但是很遗憾这样不可以
try:
    # 右边的值也需要是一个DataFrame
    df[["c", "d"]] = None
except Exception as e:
    # 我们看到这里也同样报出了相应的错误
    # 因此只有当右边的值是DataFrame的时候,df[["c", "d"]]才能具备创建新字段的能力
    print(e)  # "None of [Index(['c', 'd'], dtype='object')] are in the [columns]"

# 如果想创建多个新字段,并且还希望通过广播的方式赋上同一个值,那么上面做法是行不通的
# 解决办法是一个字段一个字段的创建,这样百分之百是没有任何问题的,既可以df["c"]也可以df.loc[:, "c"]
# 但是也可以通过我们之前说的assign
df = df.assign(
    c=lambda x: None,
    d=lambda x: None,
)
print(df)
"""
   a   b     c     d
0  1  11  None  None
1  2  22  None  None
2  3  33  None  None
"""

# 除此之外,还有一个insert方法
# 这个方法接收:插入的位置、列名、值
# 比如我想在列c的后面插入一个新列age,值全部是18,该怎么做呢?
df.insert(df.columns.get_loc("c") + 1, "age", 18)
print(df)
"""
   a   b     c  age     d
0  1  11  None   18  None
1  2  22  None   18  None
2  3  33  None   18  None
"""
# 我们看到insert这个方法是在本地进行操作的
# 关键是第一个参数,我们希望插在c的后面,那么就必须获取c所在的索引,当然也可以直接数出来
# 通过columns.get_loc即可获取,然后再加上1即可

6. ValueError: cannot compute isin with a duplicate axis.

这个错误当初也是把我搞懵逼了半天,在复现这个异常之前,我们先来聊聊非常常用的isin

isin我们一般是对Series对象使用,判断这个序列中每一个元素是不是在另一个序列里面,下面举例说明:

import pandas as pd

s = pd.Series(["a", "b", "c", "d"])
print(s.isin(["a", "c", "e"]))
"""
0     True
1    False
2     True
3    False
dtype: bool
"""

这个方法是我们经常使用的,但是你对DataFrame使用过isin吗?我们有时候需要判断两个序列,看这两个序列中的值是否在另外两个序列里面。

import pandas as pd

df1 = pd.DataFrame({"x": ["a", "b", "c", "d"], "y": [1, 2, 3, 4]})
df2 = pd.DataFrame({"x": ["a", "B", "c", "D"], "y": [1, 2, 4, 3]})
print(df1)
"""
   x  y
0  a  1
1  b  2
2  c  3
3  d  4
"""
print(df2)
"""
   x  y
0  a  1
1  B  2
2  c  4
3  D  3
"""
print(df1[["x"]].isin(df2))
# DataFrame中有两列,所以是两列布尔值
"""
       x      y
0   True   True
1  False   True
2   True  False
3  False  False
"""
# 我们来分析一下,对于df1来说,前两行肯定是没有问题的
# 但是第三行有点诡异,我们df1的第三行的y列是3,显然3是在df2的y列当中啊,为什么是False
# 同理第4行,"d"不在df2的x列中我们知道,但是y列的4很明显在df2的y列当中,为什么是False

估计有人猜到了,那就是对DataFrame使用isin的时候,多个列之间并不是独立的。事实上,DataFrame使用isin也是根据索引来的,我们举个栗子

import pandas as pd

df1 = pd.DataFrame({"x": ["a", "b", "c", "d"], "y": [1, 2, 3, 4]})
df2 = pd.DataFrame({"x": ["a", "b", "c", "d"], "y": [1, 2, 3, 4]})
# 两个一模一样的DataFrame对象
print(df1.isin(df2))
"""
      x     y
0  True  True
1  True  True
2  True  True
3  True  True
"""
# 结果没问题,但是我们将df2的索引改变一下
df2.index = [0, 1, 3, 2]
print(df1.isin(df2))
"""
       x      y
0   True   True
1   True   True
2  False  False
3  False  False
"""
# 此时我们就看到端倪了,对于DataFrame对象来讲,isin是判断对应索引的字段的值是否相同

但是问题又来了,因为这样显然不是我们期望的结果。因为即使df2中存在,但如果索引对不上的话也没有任何意义,因此我们可以手动设置索引。

import pandas as pd

df1 = pd.DataFrame({"x": ["a", "b", "c", "d"], "y": [1, 2, 3, 4]})
df2 = pd.DataFrame({"x": ["a", "b", "d", "c"], "y": [1, 2, 4, 3]})

# 我们将x和y设置为索引不就行了,加上drop=False表示设置索引的同时,还作为列
df1 = df1.set_index(["x", "y"], drop=False)
df2 = df2.set_index(["x", "y"], drop=False)
print(df1)
"""
     x  y
x y      
a 1  a  1
b 2  b  2
c 3  c  3
d 4  d  4
"""
print(df2)
"""
     x  y
x y      
a 1  a  1
b 2  b  2
d 4  d  4
c 3  c  3
"""

print(df1.isin(df2))
"""
        x     y
x y            
a 1  True  True
b 2  True  True
c 3  True  True
d 4  True  True
"""
# 在通过all(axis=1)即可找到满足条件的值
print(df1.isin(df2).all(axis=1).values)  # [ True  True  True  True]

# 我们看到此时根据索引去找,就能够准确的定位了
# 不过细心的人可能已经发现了,这个索引是由x和y两列得到的,事实上索引如果匹配上了,那么值一定是相等的
# 所以此时就没必要在进行对比了
# 是的,所以我们可以换一种方法
df1 = pd.DataFrame({"x": ["a", "b", "c", "d"], "y": [1, 2, 3, 4]})
df2 = pd.DataFrame({"x": ["a", "b", "d", "c"], "y": [1, 2, 4, 3]})

# pandas中有一个Index类,Series和DataFrame的索引就是Index类型
# 当然Index分为好几种,但是它们继承自Index
print(type(df1.index))  # <class 'pandas.core.indexes.range.RangeIndex'>
# 根据x和y两列创建Index对象
index1 = pd.Index(df1[["x", "y"]])
index2 = pd.Index(df2[["x", "y"]])
print(index1)  # Index([('a', 1), ('b', 2), ('c', 3), ('d', 4)], dtype='object')
print(index2)  # Index([('a', 1), ('b', 2), ('d', 4), ('c', 3)], dtype='object')

# Index对象可以像集合一样,取并集、交集,当然此时我们可以直接使用isin
# 因为它们整体变成了一个元组,也就是说,此时是一个一维序列,对于一维序列可以直接使用isin
# 直接返回一个numpy中的array
print(index1.isin(index2))  # [ True  True  True  True]

然而这么做有一个弊端,没错,我要复现我们上面的异常了

import pandas as pd

df1 = pd.DataFrame({"x": ["a", "b", "c", "d"], "y": [1, 2, 3, 4]})
df2 = pd.DataFrame({"x": ["a", "a", "c", "d"], "y": [1, 1, 3, 4]})

# 我们将x和y设置为索引不就行了,加上drop=False表示设置索引的同时,还作为列
df1 = df1.set_index(["x", "y"], drop=False)
df2 = df2.set_index(["x", "y"], drop=False)

try:
    print(df1.isin(df2))
except Exception as e:
    print(e)  # cannot compute isin with a duplicate axis.

# 我们对一个DataFrame使用isin,那么要求isin里面的DataFrame的索引是不可以重复的,否则就会报出上面这个错误
# 解决办法是使用pd.Index
print(pd.Index(df1[["x", "y"]]).isin(pd.Index(df2[["x", "y"]])))  # [ True False  True  True]

# 然鹅,我记得pd.Index这种做法也不保险
# 由于索引的特殊性,好像这种情况我记得也报错,但是目前没有
# 因此最稳妥的办法是再转成Series
s1 = pd.Series(pd.Index(df1[["x", "y"]]))
s2 = pd.Series(pd.Index(df2[["x", "y"]]))

print(s1)
"""
0    (a, 1)
1    (b, 2)
2    (c, 3)
3    (d, 4)
dtype: object
"""
print(s2)
"""
0    (a, 1)
1    (a, 1)
2    (c, 3)
3    (d, 4)
dtype: object
"""
print(s1.isin(s2))
"""
0     True
1    False
2     True
3     True
dtype: bool
"""
# 这种做法是百分之百没有问题的


# 忘记说了,df1.isin(df2)的时候,两个列的名称一定要对应
df1 = pd.DataFrame({"x": ["a", "b", "c", "d"], "y": [1, 2, 3, 4]})
df2 = pd.DataFrame({"a": ["a", "a", "c", "d"], "y": [1, 1, 3, 4]})
print(df1.isin(df2))
"""
       x      y
0  False   True
1  False  False
2  False   True
3  False   True
"""
# 由于df2中没有x这一列,因此相当于NaN,所以结果为False
print(df1[["y"]].isin(df2))
"""
       y
0   True
1  False
2   True
3   True
"""
# 会自动找df2中名称为y的列进行比较,因此记得注意列名
# 当然由于df1.isin(df2)在索引方面的局限性,我们一般也不会使用这种方法
# 而是会将DataFrame的每一个字段的值拼接成一个元组,整体得到一个Series对象
# 然后对Series对象使用isin,这是最正确的做法

7. ValueError: cannot set a frame with no defined index and a scalar

这个错误不是很常见,我们来看一下。

import pandas as pd

df = pd.DataFrame({"a": [1, 1, 1, 1], "b": [1, 1, 1, 1]})

df.loc[df["a"] > 2, "c"] = 1
print(df)
"""
   a  b   c
0  1  1 NaN
1  1  1 NaN
2  1  1 NaN
3  1  1 NaN
"""

我们将df["a"] > 2的记录选出来,然后同时创建"c"这一列,并设置对应的记录为1。如果不满足条件,那么会自动为NaN,而我们没有满足条件的记录,所以全部为NaN

import pandas as pd

df = pd.DataFrame({"a": [1, 1, 1, 1], "b": [1, 1, 1, 1]})

df.loc[:, "c"] = 1
print(df)
"""
   a  b  c
0  1  1  1
1  1  1  1
2  1  1  1
3  1  1  1
"""

上面这种赋值方式也是可以的,我们之前说,对于一维序列,df["xx"]等价于df.loc[:, "xx"],但实际上还是有点区别的,那就是后者要求DataFrame不可以为空

import pandas as pd

df = pd.DataFrame({"a": [], "b": []})
print(df)
"""
Empty DataFrame
Columns: [a, b]
Index: []
"""

try:
    df.loc[:, "c"] = 1
except Exception as e:
    print(e)  # cannot set a frame with no defined index and a scalar

# 空DataFrame的话,只能用df["c"] = 1的方式
df["c"] = 1
print(df)
"""
Empty DataFrame
Columns: [a, b, c]
Index: []
"""

8. ValueError: If using all scalar values, you must pass an index

这个错误应该遇见的比较少,我们看看这种错误是怎么发生的。

import pandas as pd

# 我们说通过字典构建DataFrame,value应该是序列,不应该是一个标量
try:
    df = pd.DataFrame({"a": 123, "b": None})
except Exception as e:
    print(e)  # If using all scalar values, you must pass an index

# 如果传递标量的话,那么应该同时指定一个index, index是只有一个元素的列表,里面是一个索引
df = pd.DataFrame({"a": 123, "b": None}, index=["索引"])
print(df)
"""
      a     b
索引  123  None
"""

有待发掘。。。。