由另一列中的数值引导的一列中的完整字符串值

时间:2019-11-12 07:14:06

标签: python pandas

我有一个数据框:

ID    URINE_TEST   UNIT  VALUE 
1         'alb'    mg    1500 
2         'alb'    mg    1200 
3         'alb'    mg    1600 
4         'alb'    g     1.2 
5         'alb'    g     1.8 
7         'alb'    NaN   1300 <- should become mg
8         'crt'    l     2.3 
9         'crt'    l     3.3
10        'crt'    l     4.1 
11        'crt'    ml    2500 
12        'crt'    ml    3400 
13        'crt'    ml    2100 
14        'crt'    NaN   3.0  <-should become l
15        'crt'    NaN   99  <-should stay as NaN (not inside any range)

我想完成UNIT列为NaN的地方。

我在这里说明。让我们以尿液测试“ alb”(白蛋白)为例。如您所见,有一个测试结果(ID:7)没有指定单位。但是,仅通过查看其他单位(mg和g)的VALUE范围,就可以看出该NaN应该为mg。因为其值1300的值类似于mg的值(请参见表)。即它在[mg]值[1200-1600]的值范围内。

与尿液“ crt”(肌酐)(ID:14)相同,其中NaN单位应为l(升),只需查看单位l的值范围即可:[2.3-4.1]。最后,ID:15应该保留为NaN(不属于任何值范围)。

话虽如此,我想编写一个程序,如果该值与其他单位在同一范围内,则为大数据集中的所有尿液测试分配相应的单位。否则,将单位保留为NaN。

我已经开始做df.groupby([urine_test, unit]).value.transform('min')和'max':

ID    URINE_TEST   UNIT  VALUE  MIN     MAX
1         'alb'    mg    1500   1200    1600
2         'alb'    mg    1200   1200    1600
3         'alb'    mg    1600   1200    1600
4         'alb'    g     1.2    1.2     1.8
5         'alb'    g     1.8    1.2     1.8
7         'alb'    NaN   1300 
8         'crt'    l     2.3    2.3     4.1
9         'crt'    l     3.3    2.3     4.1
10        'crt'    l     4.1    2.3     4.1
11        'crt'    ml    2500   2100    3400
12        'crt'    ml    3400   2100    3400
13        'crt'    ml    2100   2100    3400 
14        'crt'    NaN   3.0  
15        'crt'    NaN   99

但是我无法真正找到一种方法。任何帮助表示赞赏。

4 个答案:

答案 0 :(得分:1)

如果ID值是唯一的解决方案:

#filter NaNs rows by UNIT
df1 = df[df['UNIT'].isna()]
print (df1)
    ID URINE_TEST UNIT   VALUE
5    7      'alb'  NaN  1300.0
12  14      'crt'  NaN     3.0
13  15      'crt'  NaN    99.0

#aggregate min and max values
df2 = df.groupby(['URINE_TEST', 'UNIT']).VALUE.agg(['min','max']).reset_index()
print (df2)
  URINE_TEST UNIT     min     max
0      'alb'    g     1.2     1.8
1      'alb'   mg  1200.0  1600.0
2      'crt'    l     2.3     4.1
3      'crt'   ml  2100.0  3400.0

#join together and filter between values, create Series of UNIT values
df3 = df1.merge(df2, on='URINE_TEST', suffixes=('_',''))
s = df3[df3['VALUE'].between(df3['min'], df3['max'])].set_index(['ID'])['UNIT']
print (s)
ID
7     mg
14     l
Name: UNIT, dtype: object

#replace NaNs with s Series
df['UNIT'] = df['ID'].map(s).fillna(df['UNIT'])

或者:

m = df['UNIT'].isna()
df.loc[m, 'UNIT'] = df.loc[m, 'ID'].map(s)

print (df)
    ID URINE_TEST UNIT   VALUE
0    1      'alb'   mg  1500.0
1    2      'alb'   mg  1200.0
2    3      'alb'   mg  1600.0
3    4      'alb'    g     1.2
4    5      'alb'    g     1.8
5    7      'alb'   mg  1300.0
6    8      'crt'    l     2.3
7    9      'crt'    l     3.3
8   10      'crt'    l     4.1
9   11      'crt'   ml  2500.0
10  12      'crt'   ml  3400.0
11  13      'crt'   ml  2100.0
12  14      'crt'    l     3.0
13  15      'crt'  NaN    99.0

使用merge并左联接的解决方案是最通用的:

df1 = df[df['UNIT'].isna()]
df2 = df.groupby(['URINE_TEST', 'UNIT']).VALUE.agg(['min','max']).reset_index()

df3 = df1.merge(df2, on='URINE_TEST', suffixes=('_',''))
df3 = df3.loc[df3['VALUE'].between(df3['min'], df3['max']), ['URINE_TEST','VALUE', 'UNIT']]
df3 = df1.merge(df3, on=['URINE_TEST','VALUE'], suffixes=('_',''), how='left')
print (df3)
   ID URINE_TEST UNIT_   VALUE UNIT
0   7      'alb'   NaN  1300.0   mg
1  14      'crt'   NaN     3.0    l
2  15      'crt'   NaN    99.0  NaN

df = (pd.concat([df.dropna(subset=['UNIT']), df3[df.columns]])
        .sort_values('URINE_TEST')
        .reset_index(drop=True))
print (df)
    ID URINE_TEST UNIT   VALUE
0    1      'alb'   mg  1500.0
1    2      'alb'   mg  1200.0
2    3      'alb'   mg  1600.0
3    4      'alb'    g     1.2
4    5      'alb'    g     1.8
5    7      'alb'   mg  1300.0
6    8      'crt'    l     2.3
7    9      'crt'    l     3.3
8   10      'crt'    l     4.1
9   11      'crt'   ml  2500.0
10  12      'crt'   ml  3400.0
11  13      'crt'   ml  2100.0
12  14      'crt'    l     3.0
13  15      'crt'  NaN    99.0

通过df1中的唯一undex进行匹配的替代项:

df1 = df[df['UNIT'].isna()]
df2 = df.groupby(['URINE_TEST', 'UNIT']).VALUE.agg(['min','max']).reset_index()

#add index to columns by reset_index()
df3 = df1.reset_index().merge(df2, on='URINE_TEST', suffixes=('_',''))
s = df3[df3['VALUE'].between(df3['min'], df3['max'])].set_index(['index'])['UNIT']
print (s)
index
5     mg
12     l
Name: UNIT, dtype: object

df['UNIT'] = df['UNIT'].fillna(s)
print (df)
    ID URINE_TEST UNIT   VALUE
0    1      'alb'   mg  1500.0
1    2      'alb'   mg  1200.0
2    3      'alb'   mg  1600.0
3    4      'alb'    g     1.2
4    5      'alb'    g     1.8
5    7      'alb'   mg  1300.0
6    8      'crt'    l     2.3
7    9      'crt'    l     3.3
8   10      'crt'    l     4.1
9   11      'crt'   ml  2500.0
10  12      'crt'   ml  3400.0
11  13      'crt'   ml  2100.0
12  14      'crt'    l     3.0
13  15      'crt'  NaN    99.0

答案 1 :(得分:1)

按照您的逻辑,您只能fillna使用最小-最大范围内的值,而其他NaN则保持不变。我认为您可以通过sort_valuesffillloc分配使用自定义遮罩将NaN设置回最小-最大范围之外的设置来实现它

df1 = df.sort_values(['VALUE', 'UNIT'])
m1 = df1.UNIT.shift() != df1.UNIT.shift(-1)
m2 = df1.UNIT.isna()
m3 = df1.VALUE != df1.VALUE.shift()
df1['UNIT'] = df1.UNIT.ffill()
df1.loc[m1 & m2 & m3, 'UNIT'] = np.nan
df = df1.reindex(df.index)

Out[130]:
    ID URINE_TEST UNIT   VALUE
0    1      'alb'   mg  1500.0
1    2      'alb'   mg  1200.0
2    3      'alb'   mg  1600.0
3    4      'alb'    g     1.2
4    5      'alb'    g     1.8
5    7      'alb'   mg  1300.0
6    8      'crt'    l     2.3
7    9      'crt'    l     3.3
8   10      'crt'    l     4.1
9   11      'crt'   ml  2500.0
10  12      'crt'   ml  3400.0
11  13      'crt'   ml  2100.0
12  14      'crt'    l     3.0
13  15      'crt'  NaN    99.0

答案 2 :(得分:0)

假设我正确理解了您的条件,并且您的值的数据类型为float:

# List for new unit values.

NEW_UNIT = []

# For loop that checks each row in the dataframe for its respective values.

for index, row in df.iterrows():
    if row['URINE_TEST'] == 'alb':
        if (row['VALUE'] >= 1200) and (row['VALUE'] <= 1600):
            NEW_UNIT.append('mg')
        elif (row['VALUE'] >= 1.2) and (row['VALUE'] <= 1.6):
            NEW_UNIT.append('g')
        else:
            NEW_UNIT.append(float('NaN'))
    elif row['URINE_TEST'] == 'crt':
        if (row['VALUE'] >= 2300) and (row['VALUE'] <= 4100):
            NEW_UNIT.append('ml')
        elif (row['VALUE'] >= 2.3) and (row['VALUE'] <= 4.1):
            NEW_UNIT.append('l')
        else:
            NEW_UNIT.append(float('NaN'))

# Replace unit column with the updated unit values

df['UNIT'] = NEW_UNIT

答案 3 :(得分:0)

您可以使用DataFrame.apply()函数清除数据并获得所需的结果。您可以在文档中详细了解df.apply()

一个粗略的解决方案将看起来像这样,假设数据称为urine_data

#create a dictionary of all the tests and their different options and min, max values
test_dic = {'alb': [('mg', 1200, 1800), ('g', 1.2, 1.8)], 'crt': [('l', 2.3, 4.1), ('ml', 2100, 3400)]}

#will be applied for each row in the dataframe
def fill_unit(row):
    test = row['URINE_TEST'] #get test
    value = row['VALUES']   #get value
    unit = row['UNIT']     #get initial unit

    if test in test_dic.keys():
        if test_dic[test][0][1] <= value <=test_dic[test][0][2]:
            unit = test_dic[test][0][0]
        elif test_dic[test][1][1] <= value <=test_dic[test][1][2]:
            unit = test_dic[test][1][0]

        else:
            unit = np.nan

    return unit

urine_data['UNIT'] = urine_data.apply(fill_unit, axis=1)

这将为您提供输出:

URINE_TEST  UNIT    VALUES
0   alb mg  1500.0
1   alb mg  1200.0
2   alb mg  1600.0
3   alb g   1.2
4   alb g   1.8
5   alb mg  1300.0
6   crt l   2.3
7   crt l   3.3
8   crt l   4.1
9   crt ml  2500.0
10  crt ml  3400.0
11  crt ml  2100.0
12  crt l   3.0
13  crt NaN 99.0