将数据框中的列表拆分为单独的列

3
我的数据框如下所示:
    col1     col2     col3
0  [1, a]  [1, a1]  [1, a2]
1  [2, b]  [2, b1]  [2, b2]
2  [3, c]  [3, c1]  [3, c2]

我需要让它看起来像这样:

   col1     col2     col3  col4
0  a         a1      a2    1
1  b         b1      b2    2
2  c         c1      c2    3

我的代码

import pandas as pd

d = {'col1':[[1,'a'],[2,'b'],[3,'c']],
     'col2':[[1,'a1'],[2,'b1'],[3,'c1']],
     'col3':[[1,'a2'],[2,'b2'],[3,'c2']]}

df = pd.DataFrame.from_dict(d)

到目前为止,我已尝试使用apply(pd.Series)和迭代for循环来重新分配值,但没有成功


对于 col1、col2、col3:保留第二个值,那么 col4 呢? - azro
是的,对于col1-col3,我只需要第二个值。col4是一个新列,其中包含其他三列中第一个值(原始数据框中的每一行都将在第一个位置具有相同的值)。 - Just_Some_Guy
@Just_Some_Guy 如果col1中的第一个值是1,而col2中的第一个值是2(在同一行中),那该怎么办? - Andrej Kesely
@AndrejKesely,我正在处理的数据应该是这样的;所有列中的数据应该始终保持一致。 - Just_Some_Guy
4个回答

5

这里有一种使用applymapmap的方法:

df.applymap(lambda x: x[-1]).assign(col4 = df['col1'].map(lambda x: x[0]))

2

基于评论(第一个值在该行的所有列中都相同):

print(
    df.apply(lambda x: [v[1] for v in x] + [x[0][0]], axis=1)
    .apply(pd.Series)
    .rename(columns=lambda x: "col{}".format(x + 1))
)

输出:

  col1 col2 col3  col4
0    a   a1   a2     1
1    b   b1   b2     2
2    c   c1   c2     3

或者:
df = pd.concat(
    [
        df.transform(lambda x: [v[1] for v in x], axis=1),
        df.apply(lambda x: x[0][0], axis=1).rename("col4"),
    ],
    axis=1,
)
print(df)

输出:

  col1 col2 col3  col4
0    a   a1   a2     1
1    b   b1   b2     2
2    c   c1   c2     3

1
你可以使用pandas的字符串方法来访问值:
(df.assign(col1 = df.col1.str[-1], 
           col2 = df.col2.str[-1], 
           col3 = df.col3.str[-1], 
           col4 = df.col1.str[0])
   )

  col1 col2 col3  col4
0    a   a1   a2     1
1    b   b1   b2     2
2    c   c1   c2     3

你可以使用字典推导来使它更加通用:

您可以使用字典推导式,使其更通用:

result = {col : df[col].str[-1] for col in df}
col4 = df.col1.str[0]
df.assign(**result, col4 = col4)
 
  col1 col2 col3  col4
0    a   a1   a2     1
1    b   b1   b2     2
2    c   c1   c2     3

您完全可以将其放入Python中并创建一个新的数据框:

outcome = {key: [ent[-1] for ent in value] 
           for key, value in df.items()}
col4 = {'col4' : [value[-0] for value in df.col1]}
outcome = outcome | col4 # python 3.9, for earlier {**outcome, **col4}
pd.DataFrame(outcome)
 
  col1 col2 col3  col4
0    a   a1   a2     1
1    b   b1   b2     2
2    c   c1   c2     3

1
一种numpy解决方案:
import numpy as np
import pandas as pd

df = pd.DataFrame({
    'col1': {0: [1, 'a'], 1: [2, 'b'], 2: [3, 'c']},
    'col2': {0: [1, 'a1'], 1: [2, 'b1'], 2: [3, 'c1']},
    'col3': {0: [1, 'a2'], 1: [2, 'b2'], 2: [3, 'c2']}
})
a = np.array(df.values.tolist())

new_df = pd.DataFrame(
    np.concatenate((a[..., 1], a[:, 0, 0, None]), axis=1),
    columns=[*df.columns, 'col4']
)
print(new_df)

new_df:

  col1 col2 col3 col4
0    a   a1   a2    1
1    b   b1   b2    2
2    c   c1   c2    3

通过perfplot提供一些时间信息:

perfplot of various solutions runtime

import numpy as np
import pandas as pd
import perfplot


def gen_data(n):
    df = pd.DataFrame(
        {'col1': [[1, 'a']],
         'col2': [[1, 'a1']],
         'col3': [[1, 'a2']]},
    )
    df = df.loc[np.repeat(df.index.values, n)]
    return df


def applymap(df):
    return df.applymap(lambda x: x[-1]).assign(
        col4=df['col1'].map(lambda x: x[0]))


def apply_series(df):
    return df.apply(lambda x: [v[1] for v in x] + [x[0][0]], axis=1) \
        .apply(pd.Series) \
        .rename(columns=lambda x: "col{}".format(x + 1))


def pd_concat(df):
    return pd.concat(
        [
            df.transform(lambda x: [v[1] for v in x], axis=1),
            df.apply(lambda x: x[0][0], axis=1).rename("col4"),
        ],
        axis=1,
    )


def str_accessors(df):
    return df.assign(col1=df.col1.str[-1],
                     col2=df.col2.str[-1],
                     col3=df.col3.str[-1],
                     col4=df.col1.str[0])


def str_accessors_generic(df):
    result = {col: df[col].str[-1] for col in df}
    col4 = df.col1.str[0]
    return df.assign(**result, col4=col4)


def dump_into_python(df):
    outcome = {key: [ent[-1] for ent in value]
               for key, value in df.items()}
    col4 = {'col4': [value[-0] for value in df.col1]}
    outcome = outcome | col4
    return pd.DataFrame(outcome)


def numpy_sol(df):
    a = np.array(df.values.tolist())
    return pd.DataFrame(
        np.concatenate((a[..., 1], a[:, 0, 0, None]), axis=1),
        columns=[*df.columns, 'col4']
    )


if __name__ == '__main__':
    out = perfplot.bench(
        setup=gen_data,
        kernels=[
            applymap,
            apply_series,
            pd_concat,
            str_accessors,
            str_accessors_generic,
            dump_into_python,
            numpy_sol
        ],
        labels=[
            'applymap_map (rhug123)',
            'apply_series (Andrej Kesely)',
            'pd_concat (Andrej Kesely)',
            'str_accessors (sammywemmy)',
            'str_accessors_generic (sammywemmy)',
            'dump_into_python (sammywemmy)',
            'numpy_sol (Henry Ecker)',
        ],
        n_range=[2 ** k for k in range(18)],
        equality_check=None
    )
    out.save('perfplot_results.png', transparent=False)

很好的时序。我猜如果你全部使用Python完成,可能会比Numpy更快。我的猜测是列表/转换到Numpy时的开销,然后访问字符串。当然,如果速度测试返回不同的结果,这一切都不成立。 - sammywemmy
@sammywemmy,你的直觉是正确的。虽然applymap + map仍然似乎是最好的解决方案。 - Henry Ecker
1
不错。学到了新东西。applymap在字符串处理/Python相关步骤中非常有用。谢谢。 - sammywemmy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接