Pandas:基于特定值选择性连接列

0 投票
1 回答
37 浏览
提问于 2025-04-12 19:04

我刚开始接触Pandas,遇到了一个问题,就是如何正确地把几列合并在一起。这似乎涉及到我研究过的所有其他问题。

我的数据有多个字段,前13个字段都是和序列号有关的(字段名是“sn1”到“sn13”)。这些字段之后的数据也很重要,但和我现在的问题无关。前11个字段包含了实际的序列号数据,剩下的字段是以后要解决的其他问题。这些字段名存储在一个列表中,叫做serialname_list

序列号的格式是:XXX-XXXX-XXX

唯一需要注意的是,有些序列号是:XXXA-XXX-XXX

这个“A”总是在“SN4”这个位置(也就是第4个字符)。每个字符最开始都是以ASCII码的形式存储在各自的字段里。我想写一个函数,接收这个数据框,先把ASCII码转换成字符,然后根据是否有“A”来正确格式化序列号,最后把格式化后的序列号存储在“Serial”字段中,并删除所有旧的字段。

我写的代码已经接近了,但似乎运行这两个函数时,会把之前的数据覆盖成“NAN”,这是因为序列号格式不一样。我不知道我哪里做错了,因为我以为我已经正确选择了那些==A!=A的行。

另外,从我的研究来看,我觉得逐个遍历每一行的元素是不对的,因为应该用向量化的方式来处理。我的数据似乎是可以这样处理的(行要么包含“A”,要么不包含“A”,可以随意处理),但在实际操作中,我对如何正确使用Loc、Where、Apply和Query感到困惑。

def convertserial(df):
    
    def long_serial(x):
        
        tempstring =''
         
        for i in range(len(serialname_list)-2):
            tempstring = tempstring + x['sn' + str(i+1)]
            if i == 3 or i == 7: #Follow pattern with adding dashes
                tempstring = tempstring + '-'
        return tempstring
    
    def normal_serial(x):
        
        tempstring =''

        for i in range(len(serialname_list)-2):
             tempstring = tempstring + x['sn' + str(i+1)]
             if i == 2 or i == 6: #Follow alternate pattern with adding dashes
                  tempstring = tempstring + '-'
        return tempstring
    
    df = df[serialname_list].apply(lambda x: x.map(chr)) #Convert ASCII codes to characters
    df['serial'] = df[df['sn4']=='A'].apply(long_serial,axis=1) #If A is present, longer than normal SN
    df['serial'] = df[df['sn4']!='A'].apply(normal_serial,axis=1) #If A is not present, normal
    return df

1 个回答

1
 def convertserial(df):
    df[serialname_list] = df[serialname_list].apply(lambda x: x.map(chr))
    mask = df['sn4'] == 'A'
    df['serial'] = df.apply(
        lambda x: f"{x['sn1']}{x['sn2']}{x['sn3']}-{'A-' if x['sn4'] == 'A' else ''}{x['sn5']}{x['sn6']}{x['sn7']}-{x['sn8']}{x['sn9']}{x['sn10']}",
        axis=1
    )
    df.drop(columns=serialname_list[:-1], inplace=True)
    return df

这个看起来是有效的,已经在一个样本上测试过了。它解决了几个问题:

  1. 你遇到的NaN(不是一个数字)问题,可能是因为同一列被写了两次。一次是在检查A的时候,另一次是在检查不是A的时候。这里因为没有条件检查,第二个语句的结果覆盖了第一个。
  2. 我们把样本模板写死了,所以不需要根据代码来做任何修改。
  3. 修复这个问题也有助于解决数据丢失的情况,比如在我的运行中,我发现生成结果时最后两个字符被跳过了。
Output:

   sn1  sn2  sn3  sn4  sn5  sn6  sn7  sn8  sn9  sn10
0   74   79   76   66   87   74   72   65   86    80
1   81   66   89   65   86   67   89   81   80    65
2   82   81   78   66   90   82   79   80   71    80
3   65   68   80   66   82   78   88   90   80    77
4   86   73   70   66   74   68   83   65   84    86
5   79   83   78   66   84   77   65   71   73    76
6   89   84   76   66   83   78   87   74   85    69
7   73   68   71   66   76   68   82   68   65    70
8   77   67   87   66   68   79   70   65   73    65
9   76   82   81   65   86   66   65   71   70    79
  sn10         serial
0    P    JOL-WJH-AVP
1    A  QBY-A-VCY-QPA
2    P    RQN-ZRO-PGP
3    M    ADP-RNX-ZPM
4    V    VIF-JDS-ATV
5    L    OSN-TMA-GIL
6    E    YTL-SNW-JUE
7    F    IDG-LDR-DAF
8    A    MCW-DOF-AIA
9    O  LRQ-A-VBA-GFO

关于数据生成

data = {
    'sn1': np.random.randint(65, 91, size=10), 
    'sn2': np.random.randint(65, 91, size=10),
    'sn3': np.random.randint(65, 91, size=10),
    'sn4': [65 if np.random.rand() < 0.5 else np.random.randint(65, 91) for _ in range(10)],
    'sn5': np.random.randint(65, 91, size=10),
    'sn6': np.random.randint(65, 91, size=10),
    'sn7': np.random.randint(65, 91, size=10),
    'sn8': np.random.randint(65, 91, size=10),
    'sn9': np.random.randint(65, 91, size=10),
    'sn10': np.random.randint(65, 91, size=10),
}
df = pd.DataFrame(data)
serialname_list = ['sn1', 'sn2', 'sn3', 'sn4', 'sn5', 'sn6', 'sn7', 'sn8', 'sn9', 'sn10']

撰写回答