Pandas read_csv 在传递 skip_footer 参数时忽略列的数据类型

17 投票
3 回答
35549 浏览
提问于 2025-04-18 13:24

我在尝试把一个csv文件导入到pandas的dataframe时,发现它忽略了dtype这个参数。有没有办法让pandas不要自己猜测数据类型呢?

我正在合并几个CSV文件,有时候客户的字段里会有字母,而pandas会把它当成字符串导入。当我尝试合并这两个dataframe时,就会出现错误,因为我在合并两种不同类型的数据。我需要把所有的数据都存储为字符串。

数据片段:

|WAREHOUSE|ERROR|CUSTOMER|ORDER NO|
|---------|-----|--------|--------|
|3615     |     |03106   |253734  |
|3615     |     |03156   |290550  |
|3615     |     |03175   |262207  |
|3615     |     |03175   |262207  |
|3615     |     |03175   |262207  |
|3615     |     |03175   |262207  |
|3615     |     |03175   |262207  |
|3615     |     |03175   |262207  |
|3615     |     |03175   |262207  |

导入代码:

df = pd.read_csv("SomeFile.csv", 
                 header=1,
                 skip_footer=1, 
                 usecols=[2, 3], 
                 dtype={'ORDER NO': str, 'CUSTOMER': str})

df.dtypes 输出的是:

ORDER NO    int64
CUSTOMER    int64
dtype: object

3 个回答

0

看起来上面Ripster的回答解决了提问者的问题。不过对我来说,虽然这对某些人来说可能很明显,但我的问题是我在CSV文件中的表头/列名都是大写,而在代码里的dtype={...}中我却用了小写。后来我把它们都改成大写,结果read_csv不再忽略我明确的类型设置了。我平时用SQL,那里大多数情况下列名的大小写是无所谓的。这让我浪费了几个小时...

9

很遗憾,使用转换器或者更新版本的pandas并不能解决一个更普遍的问题,那就是总是确保read_csv不会把数据推断为float64类型。在pandas 0.15.2版本中,下面这个例子展示了一个包含十六进制整数和空值的CSV文件,使用转换器的目的反而干扰了数据类型的指定。

In [1]: df = pd.DataFrame(dict(a = ["0xff", "0xfe"], b = ["0xfd", None], c = [None, "0xfc"], d = [None, None]))
In [2]: df.to_csv("H:/tmp.csv", index = False)
In [3]: ef = pd.read_csv("H:/tmp.csv", dtype = {c: object for c in "abcd"}, converters = {c: lambda x: None if x == "" else int(x, 16) for c in "abcd"})
In [4]: ef.dtypes.map(lambda x: x)
Out[4]:
a      int64
b    float64
c    float64
d     object
dtype: object

在这个例子中,指定的数据类型为对象类型(object)只对全是空值的那一列有效。在这种情况下,float64类型的值可以被转换为整数,但根据鸽巢原理,并不是所有64位的整数都能表示为float64。

我找到的最好的解决方案是让pandas把可能有问题的列当作字符串来读取,正如之前提到的那样,然后再对需要转换的部分进行转换(而不是直接对整列进行转换,因为那样又会导致自动推断为float64类型)。

In [5]: ff = pd.read_csv("H:/tmp.csv", dtype = {c: object for c in "bc"}, converters = {c: lambda x: None if x == "" else int(x, 16) for c in "ad"})
In [6]: ff.dtypes
Out[6]:
a     int64
b    object
c    object
d    object
dtype: object
In [7]: for c in "bc":
   .....:     ff.loc[~pd.isnull(ff[c]), c] = ff[c][~pd.isnull(ff[c])].map(lambda x: int(x, 16))
   .....:
In [8]: ff.dtypes
Out[8]:
a     int64
b    object
c    object
d    object
dtype: object
In [9]: [(ff[c][i], type(ff[c][i])) for c in ff.columns for i in ff.index]
Out[9]:
[(255, numpy.int64),
 (254, numpy.int64),
 (253L, long),
 (nan, float),
 (nan, float),
 (252L, long),
 (None, NoneType),
 (None, NoneType)]

根据我的了解,至少在0.15.2版本之前,没有办法避免在这种情况下对字符串值进行后处理。

25

Pandas 0.13.1这个版本在处理数据时,默默地忽略了dtype这个参数,因为它使用的c engine不支持skip_footer这个功能。这就导致Pandas退回到了python engine,而这个引擎又不支持dtype

解决办法是什么呢?可以使用converters

df = pd.read_csv('SomeFile.csv', 
                 header=1,
                 skip_footer=1, 
                 usecols=[2, 3], 
                 converters={'CUSTOMER': str, 'ORDER NO': str},
                 engine='python')

输出结果:

In [1]: df.dtypes
Out[2]:
CUSTOMER    object
ORDER NO    object
dtype: object

In [3]: type(df['CUSTOMER'][0])
Out[4]: str

In [5]: df.head()
Out[6]:
  CUSTOMER ORDER NO
0    03106   253734
1    03156   290550
2    03175   262207
3    03175   262207
4    03175   262207

原始文件中的前导零被保留下来,所有数据都以字符串的形式存储。

撰写回答