pandas 即使存在匹配项,合并仍返回空df

rmbxnbpk  于 2022-11-20  发布在  其他
关注(0)|答案(1)|浏览(194)

对Pandas来说,这是一个新的概念,但我正在做的应该足够简单。我只是尝试合并3列上的两个 Dataframe 。我验证了在笔记本的第87行应该有一个匹配。事实上,应该有很多匹配,但返回的只是一个空的 Dataframe 。

我尝试过很多种合并的方法,包括weatherdf.merge和pd.merge,虽然它确实为外部合并样式返回了一些东西,但显然不是我想要的,它充满了NaN值。列的数据类型是相同的。

ryevplcw

ryevplcw1#

可能原因1:检查不匹配的键列数据类型

merge返回空帧的一个可能原因是weatherdfgaldf的键列的数据类型不匹配。要确定是否是这种情况,可以尝试以下操作:

key_cols = ["Store No", "Date", "Hour"]
for idx, (left_dtype, right_dtype) in enumerate(
    zip(weatherdf[key_cols].dtypes.values, galdf[key_cols].dtypes.values)
):
    if left_dtype != right_dtype:
        print(key_cols[idx])

上面的代码将打印具有不匹配数据类型的键列的名称。
如果是这种情况,那么您可以使用以下策略之一来解决问题。

解决方案1(更简单的解决方案)

解决此问题的最简单方法是手动将列的数据类型更改为通用类型。
例如,假设"Store No"列在weatherdf中表示为string(或object),在galdf中表示为integer。在这种情况下,您可以在尝试合并两个 Dataframe 之前将"Store No"weatherdf转换为整数:

import pandas as pd

galdf = pd.DataFrame(
    {'Store No': [500, 100, 500],
     'Date': ['2022-01-01', '2022-01-02', '2022-01-03'],
     'Hour': [7, 8, 7],
    }
).convert_dtypes()

weatherdf = pd.DataFrame(
    {'Store No': ['500', '100', '500'],
     'Date': ['2022-01-01', '2022-01-02', '2022-01-03'],
     'Hour': [7, 8, 7],
    }
).convert_dtypes()

pd.merge(weatherdf, galdf, on=['Store No', 'Date', 'Hour'], how='inner')
# Returns:
# Empty DataFrame
# Columns: [Store No, Date, Hour]
# Index: []

# -- Converting weatherdf['Store No'] from string to int -----------------------
weatherdf['Store No'] = weatherdf['Store No'].astype(int)
pd.merge(weatherdf, galdf, on=['Store No', 'Date', 'Hour'], how='inner')
# Returns:
#    Store No        Date  Hour
# 0       500  2022-01-01     7
# 1       100  2022-01-02     8
# 2       500  2022-01-03     7

解决方案2(不推荐)

解决这个问题的一个方法是,以更程式化的方式,建立一个函数,在两个数据框中寻找具有共同名称的栏,并尝试将它们转换成共同的资料类型。以下是这种函数的一个撰写不佳的实作:

import pandas as pd

def normalize_dtypes(ldf, rdf):
    """Normalize the dtypes of columns with same name from two DataFrames.

    Parameters
    ----------
    ldf : pd.DataFrame
        The left DataFrame.
    rdf : pd.DataFrame
        The right DataFrame.

    Returns
    -------
    Tuple[pd.DataFrame, pd.DataFrame]
        The left and right DataFrames with normalized common columns dtypes.

    Notes
    -----
    If a column name from one of the dataframes is named differently on the
    other, you will need to rename the column before calling this function.
    """

    ldf = ldf.convert_dtypes()
    rdf = rdf.convert_dtypes()

    common_cols = ldf.columns.intersection(rdf.columns)

    for col in common_cols:

        ldtype = ldf[col].dtype.name
        rdtype = rdf[col].dtype.name

        if ldtype != rdtype:
            if ldtype == 'string':
                try:
                    ldf[col] = ldf[col].astype(rdtype)
                except Exception:  # noqa
                    if 'Float' in rdtype and ldf[col].str.split(
                            '.').str.len().max() == 1:
                        rdf[col] = rdf[col].round(0).astype(int)
                    rdf[col] = rdf[col].astype(ldtype)
                    if ldf[col].str.len().nunique() == 1:
                        if rdf[col].str.len().max() > ldf[col].str.len().max():
                            rdf[col] = rdf[col].str[:ldf[col].str.len().max()]
                        else:
                            maxlen = max([ldf[col].str.len().max(),
                                          rdf[col].str.len().max()])
                            _rdf1 = rdf[col].str.zfill(maxlen)
                            _ldf1 = ldf[col].str.zfill(maxlen)
                            _rdf2 = rdf[col].str.rjust('0', maxlen)
                            _ldf2 = ldf[col].str.rjust('0', maxlen)
                            c1 = _rdf1.isin(_ldf1).count()
                            c2 = _rdf2.isin(_ldf2).count()
                            rdf[col] = _rdf1 if c1 > c2 else _rdf2
            elif rdtype == 'string':
                try:
                    rdf[col] = rdf[col].astype(ldtype)
                except Exception:  # noqa
                    if 'Float' in ldtype and rdf[col].str.split(
                            '.').str.len().max() == 1:
                        ldf[col] = ldf[col].round(0).astype(int)
                    ldf[col] = ldf[col].astype(rdtype)
                    if rdf[col].str.len().nunique() == 1:
                        if ldf[col].str.len().max() > rdf[col].str.len().max():
                            ldf[col] = ldf[col].str[:rdf[col].str.len().max()]
                        else:
                            maxlen = max([ldf[col].str.len().max(),
                                          rdf[col].str.len().max()])
                            _rdf1 = rdf[col].str.zfill(maxlen)
                            _ldf1 = ldf[col].str.zfill(maxlen)
                            _rdf2 = rdf[col].str.rjust('0', maxlen)
                            _ldf2 = ldf[col].str.rjust('0', maxlen)
                            c1 = _ldf1.isin(_rdf1).count()
                            c2 = _ldf2.isin(_rdf2).count()
                            ldf[col] = _ldf1 if c1 > c2 else _ldf2
            elif 'Float' in ldtype and 'Int' in rdtype:
                ldf[col] = ldf[col].round(0).astype(rdtype)

            elif 'Float' in rdtype and 'Int' in ldtype:
                rdf[col] = rdf[col].round(0).astype(ldtype)
    return ldf, rdf

下面是一个尝试合并weatherdfgaldf的示例,分别使用和不使用normalize_dtypes函数:

不使用normalize_dtypes函数:

import pandas as pd

galdf = pd.DataFrame(
    {'Store No': [500, 100, 500],
     'Date': ['2022-01-01', '2022-01-02', '2022-01-03'],
     'Hour': [7, 8, 7],
    }
).convert_dtypes()

weatherdf = pd.DataFrame(
    {'Store No': ['500', '100', '500'],
     'Date': ['2022-01-01', '2022-01-02', '2022-01-03'],
     'Hour': ['07', '08', '07'],
    }
).convert_dtypes()

pd.merge(weatherdf, galdf, on=['Store No', 'Date', 'Hour'], how='inner')
# Returns:
# Empty DataFrame
# Columns: [Store No, Date, Hour]
# Index: []

使用normalize_dtypes函数:

galdf, weatherdf = normalize_dtypes(galdf, weatherdf)
pd.merge(galdf, weatherdf, on=['Date', 'Store No', 'Hour'], how='inner')
# Returns:
#    Store No        Date  Hour
# 0       500  2022-01-01     7
# 1       100  2022-01-02     8
# 2       500  2022-01-03     7

可能原因2:两个数据框之间没有公共值

您绝对确定galdfweatherdf上有共同的值吗?
您可以尝试使用较小的列集作为on参数,并查看是否返回非空 Dataframe 。这可以帮助您查明导致merge返回空 Dataframe 的列。例如:

galdf = pd.DataFrame(
    {'Store No': ['500', '100', '500'],
     'Date': ['2022-01-01', '2022-01-02', '2022-01-03'],
     'Hour': [7, 8, 7],
    }
)

weatherdf = pd.DataFrame(
    {'Store No': ['500', '100', '500'],
     'Date': ['2022-01-01', '2022-01-02', '2022-01-03'],
     'Hour': ['07', '08', '07'],
    }
)
pd.merge(galdf, weatherdf, on=['Date', 'Store No'], how='inner')
# Returns:
#   Store No        Date  Hour_x Hour_y
# 0      500  2022-01-01       7     07
# 1      100  2022-01-02       8     08
# 2      500  2022-01-03       7     07

在上面的例子中,我们已经从合并操作中删除了"Hour"列。这样做,合并返回了一些行。这意味着由于"Hour"列中的一些不匹配的值,合并可能返回了一个空的 Dataframe 。

相关问题