根据列表中的部分字符串筛选pandas(python)数据框

3

我有一个pandas数据帧,其中包含99列的dx1-dx99和99列的px1-px99。这些列的内容是4到8个字符和数字长度不等的代码。

我想要从这些列中仅过滤出那些内容,其前三个字符与提供的列表中的三个字符匹配。提供的列表包含只有三个字符的字符串。

我生成的提供的列表长度动态且非常长。因此,我必须将整个列表作为一个单独的字符串传递。

例如,我有这个数据框:

df = pd.DataFrame({'A': 'foo bar one123 bar foo one324 foo 0'.split(),
                   'B': 'one546 one765 twosde three twowef two234 onedfr three'.split(),
                   'C': np.arange(8), 'D': np.arange(8) * 2})
    print(df)

        A       B  C   D
0     foo  one546  0   0
1       0  one765  1   2
2  one123  twosde  2   4
3     bar   three  3   6
4     foo  twowef  4   8
5  one324  two234  5  10
6     foo  onedfr  6  12
7       0   three  7  14

填充的单元格是对象类型,所有的零原本都是NULL,我用pd.fillna(0)将它们填充为零。

当我执行以下操作时:

keep = df.iloc[:,:].isin(['one123','one324','twosde','two234']).values
df.iloc[:,:] = df.iloc[:,:].where(keep, 0)
print(df)

我收到了这个信息:
        A       B  C  D
0       0       0  0  0
1       0       0  0  0
2  one123  twosde  0  0
3       0       0  0  0
4       0       0  0  0
5  one324  two234  0  0
6       0       0  0  0
7       0       0  0  0

但是,我希望传递一个包含部分字符串的列表,而不是传递单个字符串'one123'、'one324'、'twosde'、'two234'等等。

startstrings = ['one', 'two']

keep = df.iloc[:,:].contains(startstrings)
df.iloc[:,:] = df.iloc[:,:].where(keep, 0)
print(df)

但上述方法不可行。我想保留所有以“one”或“two”开头的内容。

有什么好的思路来实现吗?我的数据集非常庞大,因此效率是很重要的。


您是否有其他以名称命名的列,而不是 dx1-dx99px1-px99 - Divakar
嘿 - 没有时间发表完整的答案,但是请查看此回答以及有关 numpy.in1d 的文档:https://dev59.com/QWIk5IYBdhLWcg3wI7EF https://docs.scipy.org/doc/numpy/reference/generated/numpy.in1d.html - Chuck
Divakar,我将数据框子集化以包含dx1-dx99和px1-px99列。虽然我有很多列,但这些不需要进行操作。 - Sanoj
3个回答

3

pandas中的str.contains函数接受正则表达式,可以用来测试列表中的任何项。遍历每一列并使用str.contains函数:

startstrings = ['one', 'two']
pattern = '|'.join(startstrings)

for col in df:
    if all(df[col].apply(type) == str):
        #Set any values to 0 if they don't contain value
        df.ix[~df[col].str.contains(pattern), col] = 0        
    else:
        #Column is not all strings
        df[col] = 0

生成:

      A     B  C  D
0     0  one1  0  0
1     0  one1  0  0
2  one1  two1  0  0
3     0     0  0  0
4     0  two1  0  0
5  one1  two1  0  0
6     0  one1  0  0
7     0     0  0  0

我有99个DX1-Dx99和99个Px1-Px99列,总共198列,而不仅仅是'A'和'B'。像df ['A] .str.contains(pattern)这样编写列名称是不可行的。因此,是否有一种方法可以动态地传递这些过滤器到整个数据框,而不考虑列。由于在数据框中,我可以分离出必要的列。 - Sanoj
好的,我编辑了我的答案,现在可以适用于任意列数(之前我认为你想要整行,已经修复)。 - Kewl
有什么想法,为什么我的原始数据集会出现这个错误?所有列都是对象或int64类型:TypeError: bad operand type for unary ~: 'float' - Sanoj
在我的数据框中,所有的列都是对象或int64类型。尽管在您最新的更改后我没有收到任何错误信息,但是在我的结果集中仍然得到了全部为零的返回值。然后我强制使用“df2.applymap(str)”将它们转换为字符串,但仍然得到了全部为零的返回值。 - Sanoj
可以给一个能够重现这个问题的数据框吗?我假设你的列要么都是字符串,要么都是数字,但听起来可能并非如此。你的数据中是否有一些空值或缺失值?如果有的话,你可以使用 df[df.isnull()] = '' 来处理。 - Kewl
显示剩余4条评论

0
这是一个NumPy向量化的方法 -
# From https://dev59.com/MVkT5IYBdhLWcg3wG8D4#39045337
def slicer_vectorized(a,start,end):
    b = a.view('S1').reshape(len(a),-1)[:,start:end]
    return np.fromstring(b.tostring(),dtype='S'+str(end-start))

def isin_chars(df, startstrings, start=0, stop = 3):
    a = df.values.astype(str)
    ss_arr = np.sort(startstrings)
    a_S3 = slicer_vectorized(a.ravel(), start, stop)
    idx = np.searchsorted(ss_arr, a_S3)
    mask = (a_S3 == ss_arr[idx]).reshape(a.shape)
    return df.mask(~mask,0)

def process(df, startstrings, n = 100):
    dx_names = ['dx'+str(i) for i in range(1,n)]
    px_names = ['px'+str(i) for i in range(1,n)]
    all_names = np.hstack((dx_names, px_names))
    df0 = df[all_names]
    df_out = isin_chars(df0, startstrings, start=0, stop = 3)
    return df_out

示例运行 -

In [245]: df
Out[245]: 
    dx1    dx2  px1  px2  0
0   foo   one1    0    0  0
1   bar   one1    1    2  7
2  one1   two1    2    4  3
3   bar  three    3    6  8
4   foo   two1    4    8  1
5  one1   two1    5   10  8
6   foo   one1    6   12  6
7   foo  three    7   14  6

In [246]: startstrings = ['two', 'one']

In [247]: process(df, startstrings, n = 3) # change n = 100 for actual case
Out[247]: 
    dx1   dx2  px1  px2
0     0  one1    0    0
1     0  one1    0    0
2  one1  two1    0    0
3     0     0    0    0
4     0  two1    0    0
5  one1  two1    0    0
6     0  one1    0    0
7     0     0    0    0

我有 Dx1-Dx99 和 Px1-Px99。因此,我只取了一行代码,它试图匹配前三个字符,但是我得到了这个错误:ValueError: axis(=-1) 超出范围。 - Sanoj
@Sanoj 编辑 dx_names = ['Dx'+str(i) for i in range(1,n)]px_names = ['Px'+str(i) for i in range(1,n)] 然后看看效果如何? - Divakar
抱歉,我之前都是用小写字母表示 dx1-dx99。之前类型也写错了。我一直在收到错误信息。 - Sanoj

0

这有点暴力破解的感觉,但它允许不同长度的前缀字符串,如所示。我修改了你的示例以查找 ['one1', 'th'] 以显示不同的长度。不确定这是否是你需要的。

import numpy as np
import pandas as pd

df = pd.DataFrame({'A': 'foo bar one1 bar foo one1 foo foo'.split(),
                   'B': 'one1 one1 two1 three two1 two1 one1 three'.split(),
                   'C': np.arange(8), 'D': np.arange(8) * 2})

prefixes = "one1 th".split()

matches = np.full(df.shape, False, dtype=bool)

for pfx in prefixes:
    for i,col in enumerate(df.columns):
        try:
            matches[:,i] |= df[col].str.startswith(pfx)
        except AttributeError as e:
            # Some columns have no strings
            pass

keep = df.where(matches, 0)
print(keep)

运行此代码,我得到:

$ python test.py
      A      B  C  D
0     0   one1  0  0
1     0   one1  0  0
2  one1      0  0  0
3     0  three  0  0
4     0      0  0  0
5  one1      0  0  0
6     0   one1  0  0
7     0  three  0  0

出现以下错误:<ipython-input-75-b28fd1fff9be> in <module>() 44 for i,col in enumerate(df2.columns): 45 try: ---> 46 matches[:,i] |= df2[col].str.startswith(pfx) 47 except AttributeError as e: 48 # Some columns have no stringsTypeError: ufunc 'bitwise_or' 的输出(类型码为 'O')无法按照强制转换规则 ''same_kind'' 转换为提供的输出参数(类型码为 '?')。 - Sanoj
显然这对我有效。也许尝试扩展这一行:matches [:,i] = matches [:,i] | df [col] .str.startswith(pfx)?您使用的numpy / pandas版本是什么? - aghast
Python 3.5.1 | Anaconda 2.5.0(64位)|(默认,2016年1月29日,15:01:46)[MSC v.1900 64位(AMD64)] - Sanoj

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