有没有办法获得几列pandas DataFrame的“联合”?

时间:2017-04-03 23:08:30

标签: python pandas

我不是在寻找合并/连接列或用其他值替换某些值(虽然......也许是吗?)。但我有一个大的数据帧(> 100行和列),我想提取“几乎相同”的列,即共同具有> 2值(在相同的索引处)并且在其他值上没有不同的值索引(如果一列中有值,则必须有相同的值或另一列中的NaN)。 以下是此类数据框的示例:

a = np.random.randint(1,10,10)
b = np.array([np.nan,2,np.nan,3,np.nan,6,8,1,2,np.nan])
c = np.random.randint(1,10,10)
d = np.array([7,2,np.nan,np.nan,np.nan,6,8,np.nan,2,2])
e = np.array([np.nan,2,np.nan,np.nan,np.nan,6,8,np.nan,np.nan,2])
f = np.array([np.nan,2,np.nan,3.0,7,np.nan,8,np.nan,np.nan,2])
df = pd.DataFrame({'A':a,'B':b,'C':c,'D':d,'E':e, 'F':f})
df.ix[3:6,'A']=np.nan
df.ix[4:8,'C']=np.nan

修改

keys=['S01_o4584','S02_o2531','S03_o7812','S03_o1122','S04_o5210','S04_o3212','S05_o4665','S06_o7425','S07_o3689','S08_o2371']
df['index']=keys
df = df.set_index('index')

             A    B    C    D    E    F
index                                  
S01_o4584  8.0  NaN  9.0  7.0  NaN  NaN
S02_o2531  8.0  2.0  5.0  2.0  2.0  2.0
S03_o7812  1.0  NaN  5.0  NaN  NaN  NaN
S03_o1122  NaN  3.0  6.0  NaN  NaN  3.0
S04_o5210  NaN  NaN  NaN  NaN  NaN  7.0
S04_o3212  NaN  6.0  NaN  6.0  6.0  NaN
S05_o4665  NaN  8.0  NaN  8.0  8.0  8.0
S06_o7425  1.0  1.0  NaN  NaN  NaN  NaN
S07_o3689  8.0  2.0  NaN  2.0  NaN  NaN
S08_o2371  3.0  NaN  9.0  2.0  2.0  2.0

如您所见,列B,D (和新E)在位置(索引)S02_o2531,S04_o3212,S05_o4665和S08_o2371处具有相同的值,而在其他位置,一个具有值,而在其他人有NaN。

我想要的输出是:

index   BD*E*
S01_o4584   7
S02_o2531   2
S03_o7812   NaN
S03_o1122   3
S04_o5210   NaN
S04_o3212   6
S05_o4665   8
S06_o7425   1
S07_o3689   2
S08_o2371   2

但是,我无法组合那些对于索引的同一个开头有两个不同值的列:正如您所看到的,列F也共享一些索引,但新的索引位于S04_o5210,但是先前的组合列已经具有“S04_”的值(索引S04_o3212)。

有合理的pythonic方式吗?即1)根据条件中的列找到列,它们中的值必须相同或者np.nan,而不是不同。 2)设置一个条件,如果列具有与先前包含的值的索引相同的开头,则不能组合列(我可能需要将字符串拆分为两列并进行多索引)3)将它们组合成新的系列/数据帧。

3 个答案:

答案 0 :(得分:1)

def almost(df):
    i, j = np.triu_indices(len(df.columns), 1)

    v = df.values

    d = v[:, i] - v[:, j]
    m = (np.where(np.isnan(d), 0, d) == 0).all(0)

    return pd.concat(
        [
            df.iloc[:, i_].combine_first(
                df.iloc[:, j_]
            ).rename(
                tuple(df.columns[[i_, j_]])
            ) for i_, j_ in zip(i[m], j[m])],
        axis=1
    )

almost(df)

     B
     D
0  7.0
1  2.0
2  NaN
3  3.0
4  NaN
5  6.0
6  8.0
7  1.0
8  2.0
9  2.0

如何运作

  • ij使用numpy代表每个列的组合,以获取上三角的索引。
  • 使用numpydf.values对基础i数组j进行切片并减去它们。如果差异为nan,则表示其中一个是nan。否则,如果各个元素相同,则差值应为零。
  • 因为我们可以在一个或另一个中容忍nan,所以使用np.where将其填充为零。
  • 使用(x == 0).all(0)查找所有行的归零位置。
  • 使用上面的掩码对ij进行切片,并确定匹配的列。
  • 使用pd.MultiIndex为所有匹配项构建一个数据框,用于显示匹配内容的列。

酷示例

np.random.seed([3,1415])
m, n = 20, 26
df = pd.DataFrame(
    np.random.randint(10, size=(m, n)),
    columns=list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
).mask(np.random.choice([True, False], (m, n), p=(.6, .4)))

df

enter image description here

almost(df)

      A         D    G    H    I         J    K     
      J    X    K    M    N    J    K    V    S    X
0   6.0  7.0  3.0  NaN  4.0  6.0  NaN  6.0  NaN  7.0
1   3.0  3.0  2.0  6.0  4.0  NaN  2.0  6.0  2.0  2.0
2   3.0  0.0  NaN  2.0  4.0  3.0  NaN  3.0  4.0  0.0
3   4.0  4.0  3.0  5.0  5.0  4.0  3.0  4.0  3.0  3.0
4   7.0  NaN  NaN  7.0  3.0  7.0  NaN  7.0  NaN  NaN
5   NaN  NaN  2.0  0.0  5.0  NaN  2.0  2.0  2.0  2.0
6   NaN  8.0  NaN  NaN  9.0  2.0  2.0  1.0  NaN  8.0
7   NaN  7.0  NaN  9.0  9.0  6.0  6.0  NaN  NaN  7.0
8   NaN  NaN  8.0  3.0  1.0  NaN  NaN  NaN  4.0  NaN
9   0.0  0.0  8.0  2.0  NaN  3.0  3.0  NaN  NaN  NaN
10  0.0  0.0  NaN  6.0  1.0  NaN  NaN  8.0  NaN  NaN
11  NaN  NaN  3.0  NaN  9.0  3.0  3.0  NaN  3.0  3.0
12  5.0  NaN  NaN  NaN  6.0  5.0  NaN  5.0  8.0  NaN
13  NaN  NaN  NaN  NaN  7.0  5.0  5.0  NaN  NaN  NaN
14  NaN  NaN  6.0  4.0  8.0  8.0  8.0  NaN  0.0  NaN
15  8.0  8.0  7.0  NaN  NaN  NaN  NaN  NaN  2.0  NaN
16  4.0  4.0  4.0  4.0  9.0  9.0  9.0  6.0  4.0  NaN
17  NaN  4.0  NaN  4.0  2.0  8.0  8.0  4.0  NaN  4.0
18  NaN  NaN  2.0  7.0  NaN  NaN  NaN  NaN  NaN  NaN
19  NaN  7.0  6.0  3.0  5.0  NaN  NaN  7.0  NaN  7.0

答案 1 :(得分:1)

听起来,关键是如何检测"几乎相同的"列,这些列只是缺少哪些值(如果有的话)。给定两个列名称,如何检查它们是否几乎相同?请注意,如果我们发现一个重要的差异,则它必须位于两列都没有NaN的索引处。换句话说,诀窍是丢弃具有缺失值的行并比较其余行:

tocheck = df[["B", "D"]].dropna()
if all(tocheck.B == tocheck.D):
    print("B, D are almost identical")

让我们使用它迭代所有列对,并合并匹配的列:

for a, b in itertools.combinations(df.columns, 2):
    if a not in df.columns or b not in df.columns:  # Was one deleted already?
        continue
    tocheck = df[[a, b]].dropna()
    if all(tocheck[a] == tocheck[b]):
        print(b, "->", a)
        df[a] = df[a].combine_first(df[b])
        del df[b]

注意(如果您没有注意到),当多列最终合并时,可能会有依赖于订单的行为。例如:

     A    B   C
0   NaN   1   2 
1   10   NaN NaN

您可以将BC合并到A,但不能同时合并到 = f.collection_select :item_ids, Item.order("name ASC").all, :id, :name, {}, {:multiple => true, class: "form-control"} 。除了这些问题之外,可以将多个列合并为一个,因为合并的列将保存在其中一个比较列中。

答案 2 :(得分:0)

et voila

test = df.B == df.D
df.loc[test,'myunion'] = df.loc[test, 'B']
df.loc[!test ,'myunion'] = df.loc[!test, 'B'].fillna(0) + df.loc[!test, 'D'].fillna(0)