【问题标题】:Pandas DataFrame.agg produces multiindex after selection with missing categoriesPandas DataFrame.agg 在选择缺失类别后生成多索引
【发布时间】:2021-05-26 02:02:18
【问题描述】:

我仍在研究一个可重现的示例,但不幸的是,我是 pandas 的绝对初学者,我不知道自己做错了什么。这种行为似乎是 Pandas 1.2.4 中的一个错误。

我有两个数据框,files_df 给出了几个 repos 中源代码文件的类型和复杂性,proj_df 给出了项目和最后一次更新的时间。

我正在尝试按复杂性为每种语言挑选前两个项目,它看起来像这样:

def largest(x):
    return (
        # Pick the top two rows in each language (from the groupby) by scc_complexity
        x.nlargest(2, 'scc_complexity')
        # Combine the proj_repo values (we don't actually care about scc_complexity at this point)
        .agg({'proj_repo': lambda y: '<br>'.join(y.values)})
    )

max_proj = (
    # Pick the language and complexity columns
    files_df[['scc_lang', 'scc_complexity', 'proj_repo']]
    # Sum scc_complexity by language and repo
    .groupby(['scc_lang', 'proj_repo'], observed=True).sum()
    .reset_index()
)

max_proj = (
    max_proj
    # Select only repos updated in the last two years
    .loc[max_proj['proj_repo'].isin(proj_df.loc[proj_df['last_author_time'] > '2019-05-01', 'proj_repo'])]
    # Now pick the top repos by language complexity
    .groupby('scc_lang').apply(largest)
)

这应该生成一个以scc_lang 作为其索引和proj_repo 作为其唯一列的框架,但它会生成这个形状:

<class 'pandas.core.frame.DataFrame'>
MultiIndex: 151 entries, ('ASP', 'proj_repo') to ('XML Schema (min)', 'proj_repo')
Data columns (total 2 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   0          151 non-null    object
 1   proj_repo  0 non-null      object
dtypes: object(2)
memory usage: 8.7+ KB
MultiIndex([(                        'ASP', 'proj_repo'),
            (                   'Autoconf', 'proj_repo'),
            (                       'BASH', 'proj_repo'),
            (                      'Batch', 'proj_repo'),
...

也就是说,现在有一个多索引,proj_info 列现在是一个名为0 的列,proj_info 列现在是空的。

现在是真正奇怪的部分。如果您从max_proj 中删除.loc[] 选择,此代码会生成正确的形状。对于我的一生,我无法弄清楚为什么进行选择会改变结果的形状。我无法通过一个完整的小示例重现结果。

我会不断尝试,如果我有什么有趣的发现,我会在这里报告。

轻微更新:我发现删除 observed=True 似乎也能产生正确的形状——所以这可能与缺少键有关?

更大的更新:我设法重现了它,是的,它确实与丢失的键有关,但我不确定如何:

tdf = pd.DataFrame([['lang1', 1, 'ab'],
                    ['lang2', 2, 'cd'],
                    ['lang2', 4, 'cd'],
                    ['lang2', 3, 'ef'],
                    ['lang2', 4, 'gh'],
                    ['lang3', 2, 'ef']
                   ],
                    columns=['lang', 'size', 'name']).astype({'lang': 'category'}, copy=False).set_index('lang')

d = tdf.groupby(['lang', 'name'], observed=True).sum().reset_index()
d = d.loc[d['name'].isin(pd.Series(['ab','cd','gh']))].groupby('lang').apply(lambda x: x.agg({'name': lambda y: ' '.join(y.values)}))
display(d)
d.info()
0 name
lang
lang1 name ab NaN
lang2 name cd gh NaN
<class 'pandas.core.frame.DataFrame'>
MultiIndex: 2 entries, ('lang1', 'name') to ('lang2', 'name')
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   0       2 non-null      object
 1   name    0 non-null      object
dtypes: object(2)
memory usage: 356.0+ bytes

删除“ab”或“ef”名称会触发此问题,这意味着它与缺少的类别有关。事实上,将列类型改回object 解决了这个问题。所以看来我误解了类别...

另一个更新:我已设法将其缩减为以下两行:

tdf2 = pd.DataFrame([['a', 'boof']], columns=['lang', 'name']).astype({'lang': pd.CategoricalDtype(['a', 'b'])})
display(tdf2.groupby('lang').apply(lambda x: x.agg({'name': lambda y: y.values})))

我们现在非常少。删除上述代码的几乎任何部分都会导致生成的数据框捕捉到正确的形状。

我们这里有一个 agg 被应用到由.groupby('lang') 生成的空数据帧,它恢复了已从数据中删除的类别。在apply() 中重新加入的结果似乎有些奇怪。

这里的一个新发现是,从 apply() lambda 返回 y.values 是其中的一部分:如果我返回 y(系列),则不会发生奇怪的结构。

【问题讨论】:

  • 我们可以使用提示、航班、泰坦尼克号数据集来复制您的问题吗?
  • 好主意,我看看能不能用其中一个复制它。
  • 看看我的更新。
  • 我认为你也在做一些额外的事情,d.loc[d['name'].isin(pd.Series(['ab','cd','gh']))].groupby('lang')['name'].agg(' '.join) 试试这个带有类别的语句,这会产生所需的信息吗?
  • 在底部的小例子上试过,很有趣,它产生了一个系列,其中所有三个类别都是标签,而缺失的类别的值为 None。所以这很有启发性,类别类型似乎非常僵化。不幸的是,在完整代码中,apply() 是必需的,因为 nlargest() 调用。

标签: python pandas dataframe


【解决方案1】:

终于找到了。奇怪的结构结果是因为空dataframe上的单列agg的结果是一个DataFrame,但是如果DataFrame有行,结果是一个Series

tdf2 = pd.DataFrame([], columns=['lang', 'name'])
print(type(tdf2.agg({'name': lambda y: y.values})))

tdf2 = pd.DataFrame([['a', 'boof']], columns=['lang', 'name'])
print(type(tdf2.agg({'name': lambda y: y.values})))
<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.series.Series'>

groupby 的 apply() 返回奇怪的结构,因为它试图将两者结合起来:

tdf2 = pd.DataFrame([['a', 'boof'], ['b', 'toop']], columns=['lang', 'name'])
print(tdf2.groupby('lang').apply(lambda x: x['name'] if x.iloc[0,0] == 'a' else x))
           0 lang  name
lang                   
a    0  boof  NaN   NaN
b    1   NaN    b  toop

我不知道我是否会称其为错误。这当然是令人惊讶的行为,以及一般的程序should not be surprising

更新:归档为https://github.com/pandas-dev/pandas/issues/41672

更新 2:现在我了解了类别的工作原理,对原始代码的最终修复是在应用之前将 observed=True 添加到 groupby 中,消除空数据框。

【讨论】:

    猜你喜欢
    • 2018-10-07
    • 2021-01-26
    • 2021-10-24
    • 2020-09-05
    • 1970-01-01
    • 2014-01-01
    相关资源
    最近更新 更多