当合并两个数据帧时,索引会合并某些值,但不会合并使用"外部"合并,python/pandas会自动将Null(NaN)值添加到它无法匹配的字段中.很好这是正常行为,但它会更改数据类型.这是一个问题,因为您现在必须重述列应具有的数据类型.
fillna或dropna()似乎在合并后不会立即保留数据类型.或者我需要一个桌子结构?
通常我会运行numpy np.where(field.isnull()等),但这意味着运行所有列.
什么是解决方法?
我认为没有任何真正优雅/有效的方法来做到这一点。您可以通过跟踪原始数据类型,然后在合并后转换列来实现,如下所示:
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)
从 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)
这应该真的只与一个问题bool或intdtypes。float,object并且datetime64[ns]可以保留NaN或NaT不更改类型。
因此,我建议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。