外部合并后保留Dataframe列数据类型

Jef*_*eff 15 python pandas

当合并两个数据帧时,索引会合并某些值,但不会合并使用"外部"合并,python/pandas会自动将Null(NaN)值添加到它无法匹配的字段中.很好这是正常行为,但它会更改数据类型.这是一个问题,因为您现在必须重述列应具有的数据类型.

fillna或dropna()似乎在合并后不会立即保留数据类型.或者我需要一个桌子结构?

通常我会运行numpy np.where(field.isnull()等),但这意味着运行所有列.

什么是解决方法?

hum*_*ume 7

我认为没有任何真正优雅/有效的方法来做到这一点。您可以通过跟踪原始数据类型,然后在合并后转换列来实现,如下所示:

import pandas as pd

# all types are originally ints
df = pd.DataFrame({'a': [1]*10, 'b': [1, 2] * 5, 'c': range(10)})
df2 = pd.DataFrame({'e': [1, 1], 'd': [1, 2]})

# track the original dtypes
orig = df.dtypes.to_dict()
orig.update(df2.dtypes.to_dict())

# join the dataframe
joined = df.join(df2, how='outer')

# columns with nans are now float dtype
print joined.dtypes

# replace nans with suitable int value
joined.fillna(-1, inplace=True)

# re-cast the columns as their original dtype
joined_orig_types = joined.apply(lambda x: x.astype(orig[x.name]))

print joined_orig_types.dtypes
Run Code Online (Sandbox Code Playgroud)


tot*_*ack 7

从 pandas 1.0.0 开始,我相信您还有另一个选择,那就是首先使用Convert_dtypes。这会将数据帧列转换为支持 pd.NA 的数据类型,从而避免 NaN 的问题。与这个答案不同,这也保留了布尔值。

...

df = pd.DataFrame({'a': [1]*6, 'b': [1, 2]*3, 'c': range(6)})
df2 = pd.DataFrame({'d': [1,2], 'e': [True, False]})
df = df.convert_dtypes()
df2 = df2.convert_dtypes()
print(df.join(df2))

#   a  b  c     d      e
#0  1  1  0     1   True
#1  1  2  1     2  False
#2  1  1  2  <NA>   <NA>
#3  1  2  3  <NA>   <NA>
#4  1  1  4  <NA>   <NA>
#5  1  2  5  <NA>   <NA>
Run Code Online (Sandbox Code Playgroud)


ALo*_*llz 5

这应该真的只与一个问题boolintdtypes。floatobject并且datetime64[ns]可以保留NaNNaT不更改类型。

因此,我建议Int64为您的整数或bool列使用新类型,该类型可以stroring NaN。对于布尔值,需要将其转换为1或0而不是True或False,然后转换为Int64。您应该对联接之前的所有int和bool列执行此操作,但我仅说明在联接之后df2哪些列会获得NaN行:

import pandas as pd

df = pd.DataFrame({'a': [1]*6, 'b': [1, 2]*3, 'c': range(6)})
df2 = pd.DataFrame({'d': [1,2], 'e': [True, False]})

df2 = df2.astype('int').astype('Int64')
df2.dtypes
#d    Int64
#e    Int64
#dtype: object

df.join(df2)
#   a  b  c    d    e
#0  1  1  0    1    1
#1  1  2  1    2    0
#2  1  1  2  NaN  NaN
#3  1  2  3  NaN  NaN
#4  1  1  4  NaN  NaN
#5  1  2  5  NaN  NaN

#a    int64
#b    int64
#c    int64
#d    Int64
#e    Int64
#dtype: object
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,除非需要,否则什么都不会被抛弃。例如,在其他解决方案中,如果您这样做.fillna(-1.72),可能会在调用时得到不需要的答案int(-1.72),然后将填充值强制为-1。这在某些情况下可能有用,但在其他情况下却很危险。

随着Int64填充值仍然忠实于您指定的内容和列仅上溯造型,如果你使用非INT填写。如果执行类似的操作.fillna('Missing'),它也不会抛出错误,因为它永远不会尝试将字符串类型转换为int。

  • @hume 我不同意,“fillna”是问题的外围。OP 遇到一个问题,即“int”列由于缺少值而变成“float”,因为“NaN”是浮点数,而“pandas”则向上转换该列。IMO,草率的解决方案是使用一些具有哨兵值(-999)的“fillna”;然而,这很容易出错,尤其是在做“sum”或“mean”等数学运算时。使用“Int64”,不需要指示值为空的幻数,因为“Int64”列将数字存储为整数**并且**可以存储“NaN”,从而在合并后保留数据类型。这就是问题所在。 (2认同)