2015-03-11 4 views
0

Я очищаю данные и имею набор допустимых значений для столбцов, содержащих перечисленные данные. Поэтому я хочу разбить набор данных на строки, которые являются AOK и строками, которые содержат недопустимые данные столбца. Фокус в том, что строки с недопустимыми данными столбца должны заполнять специальный столбец списком имен столбцов, которые были недопустимы для этой строки.Pandas Spit DataFrame для условий столбца и создания столбца с разрывом Причина

Например, с учетом следующей таблице:

 A  B C D 
0 foo one 0 0 
1 bar one 1 2 
2 foo two 2 4 
3 bar three 3 6 
4 foo two 4 8 
5 bar two 5 10 
6 foo one 6 12 
7 foo three 7 14 

с пределом, который Col А может быть { 'Foo'} и столбец В может быть { 'один', 'два'}. Выходные dataframes должны быть следующими:

Допустимые строки:

 A  B C D 
0 foo one 0 0 
2 foo two 2 4 
4 foo two 4 8 
6 foo one 6 12 

инвалидные строки:

 A  B C D Exception 
1 bar one 1 2 A 
3 bar three 3 6 A, B 
5 bar two 5 10 A 

Как новичок панды я пошел об этом следующим образом:

columnBounds = {'A' : {'foo'}, 'B':{'one', 'two'}} 
df['exception'] = '' 
for columnName, bounds in columnBounds.iteritems(): 
    idlist = df[~df.columnName.isin(bounds)].index.tolist() 
    for ix in idlist: 
     if df.loc[ix, 'exception'] == '': 
      df.loc[ix, 'exception'] = str(ix) 
     else: 
      df.loc[ix, 'exception'] += ', {}'.format(str(ix)) 

baddf = df[df.exception.isin([''])] 
gooddf = df[~df.exception.isin([''])] 

Этот код выглядит неправильно во многих отношениях, но в основном li п:

idlist = df[~df.columnName.isin(bounds)].index.tolist() 

терпит неудачу, как использование «ColumnName» терпит неудачу в контексте ДФА [], как это ожидает буквальное значение имени столбца. Как исправить это и/или что было правильным способом решить исходную проблему? Также существует проблема с тем, как они собираются, но я не понял, как хранить и управлять одним списком, встроенным в ячейку pandas.

Спасибо!

ответ

3

isin принимает словарь, который может упростить сложную часть значительно:

>>> good_dict = {"A": ["foo"], "B": ["one", "two"]} 
>>> invalid = ~df[list(good_dict)].isin(good_dict) 
>>> df["Exception"] = invalid.apply(lambda x: ','.join(invalid.columns[x]), axis=1) 
>>> df 
    A  B C D Exception 
0 foo one 0 0   
1 bar one 1 2   A 
2 foo two 2 4   
3 bar three 3 6  A,B 
4 foo two 4 8   
5 bar two 5 10   A 
6 foo one 6 12   
7 foo three 7 14   B 

, который может быть легко раскол:

>>> any_exception = invalid.any(axis=1) 
>>> df[any_exception] 
    A  B C D Exception 
1 bar one 1 2   A 
3 bar three 3 6  A,B 
5 bar two 5 10   A 
7 foo three 7 14   B 
>>> df[~any_exception] 
    A B C D Exception 
0 foo one 0 0   
2 foo two 2 4   
4 foo two 4 8   
6 foo one 6 12   

Мне нравится иметь пустой столбец исключения для тех, мы но мы могли бы избежать этого, если бы захотели.

+0

Спасибо. Если бы я знал, что 'isin' принял' dict', мой код был бы проще. Урок выучен! –

+0

Спасибо! Это решение прекрасно использует инструменты для вырезания сложности. – Colin

1

~df.columnName.isin(bounds) возвращает логическое значение (True, False). Сначала вам нужно выполнить проверку, а затем добавить свои идентификаторы. В зависимости от того, как вы читаете данные, вы можете пройти через строки и проверить свои исключения и добавить их; или вы читаете их в другой фрейм данных, чтобы получить два ваших фрейма.

+0

Повторение назад: сначала запустить ~ df.columnName.isin (bounds), чтобы получить фрейм данных с значениями T/F вместе с столбцом индекса, идентичным исходному фреймворку данных. Затем используйте столбец индекса в производном фрейме данных, чтобы получить столбец исключений в исходном фрейме данных? – Colin

1

Вот решение, которое работает:

import pandas as pd 

def set_exception(row, col): 
    if row['Exception'] is None: 
     row['Exception'] = [col] 
    else: 
     row['Exception'].append(col) 


def f(row, allowed_col_vals): 
    for col in row.keys(): 
     if col in allowed_col_vals: 
      if row[col] not in allowed_col_vals[col]: 
       set_exception(row, col) 
    return row 


allowed_col_vals = { 
    'A': ['foo'], 
    'B': ['one', 'two'] 
} 

df = pd.read_csv('data.csv') 
df['Exception'] = None 
# apply f to each row of df 
df = df.apply(f, axis=1, args=(allowed_col_vals,)) 
# df['Exception'] is a Series and map applies the function element-wise 
valid_rows = df[df['Exception'].map(lambda x: not bool(x))] 
invalid_rows = df[df['Exception'].map(bool)] 

с выходом как:

# valid rows: 
    A B C D Exception 
0 foo one 0 0  None 
2 foo two 2 4  None 
4 foo two 4 8  None 
6 foo one 6 12  None 

# invalid rows:  
    A  B C D Exception 
1 bar one 1 2  [A] 
3 bar three 3 6 [A, B] 
5 bar two 5 10  [A] 
7 foo three 7 14  [B] 
+0

Выглядит отлично. Единственная проблема заключается в том, что f() строит имена столбцов. Есть ли способ сделать это, когда имена столбцов явно не определены в коде? – Colin

+0

Для серии 'row' все столбцы хранятся в' row.keys() ', поэтому вы можете прокручивать строку' row.keys() 'и обрабатывать каждый столбец соответственно. – zyxue

+0

Ключи у нас есть. Проблемными строками являются «row.A! = 'Foo» и «row.B not in [' one ',' two ']', поскольку это кодирует имена столбцов A и B в исходный код и вытягивает их из внешнего источника , – Colin

2

Вот как я бы это сделать. Первая программа, затем вывод, затем объяснение.

from pandas import DataFrame 
from itertools import compress 

# define DataFrame 

rows = [ 
    ["foo", "one", 0, 0], 
    ["bar", "one", 1, 2], 
    ["foo", "two", 2, 4], 
    ["bar", "three", 3, 6], 
    ["foo", "two", 4, 8], 
    ["bar", "two", 5, 10], 
    ["foo", "one", 6, 12], 
    ["foo", "three", 7, 14], 
] 

df = DataFrame(data=rows, columns=list("ABCD")) 

print "original DataFrame:" 
print df, "\n" 

# define what values are permitted in each column 
permitted = { 
    'A': set(["foo"]), 
    'B': set(["one", "two"]), 
} 

def check_validity(df, permitted): 
    """ 
    Given a DataFrame and a dict of permitted values for 
    each column, determine which cells are valid given 
    those rules. Amend the DataFrame to note which rows have 
    exceptions. Return a second DataFrame that indicates which 
    cells were valid. 
    """ 

    # first determine, for each column in the list of rules, what 
    # cells are valid/invalid by that rule 
    valid_cols = [ colname for colname in df.columns if colname in permitted ] 
    valid = DataFrame(columns=valid_cols, index=df.index) 
    for colname, permitted_values in permitted.items(): 
     valid[colname] = df[colname].isin(permitted_values) 

    # add an Exception column that for each row, lists just the columns 
    # that were found NOT to be valid 
    df["Exception"] = [ ', '.join(compress(valid.columns, ~valid.ix[i])) for i in df.index ] 
    return valid 


valid = check_validity(df, permitted) 

print "exceptions noted:" 
print df, "\n" 

valid_rows = valid["A"] & valid["B"] 

# the good kids 
print "valid data:" 
print df[valid_rows], "\n" 

# the problem children 
print "not valid:" 
print df[~valid_rows], "\n" 

Урожайность:

original DataFrame: 
    A  B C D 
0 foo one 0 0 
1 bar one 1 2 
2 foo two 2 4 
3 bar three 3 6 
4 foo two 4 8 
5 bar two 5 10 
6 foo one 6 12 
7 foo three 7 14 

exceptions noted: 
    A  B C D Exception 
0 foo one 0 0 
1 bar one 1 2   A 
2 foo two 2 4 
3 bar three 3 6  A, B 
4 foo two 4 8 
5 bar two 5 10   A 
6 foo one 6 12 
7 foo three 7 14   B 

valid data: 
    A B C D Exception 
0 foo one 0 0 
2 foo two 2 4 
4 foo two 4 8 
6 foo one 6 12 

not valid: 
    A  B C D Exception 
1 bar one 1 2   A 
3 bar three 3 6  A, B 
5 bar two 5 10   A 
7 foo three 7 14   B 

check_validity функция является ключом к операции. Он просматривает каждый столбец, используя метод isin для тестирования набора элементов. Он конструирует второй DataFrame, valid, чтобы записать, какие ячейки проходят или не проходят тест. Затем он использует очень удобный itertools.compress, чтобы выбрать только имена столбцов, которые pandas удивительная функция выбора (~valid.ix[rownumber]), чтобы вытащить «элементы, которые недействительны в этой строке» и присоединиться к ним. Соберите этот список недопустимых элементов в строке по всему DataFrame, и мы вернемся домой.

+0

Выглядит правильно. У меня есть «разрешенный» дикт, но я оставил его для простоты, но я добавлю это для ясности. – Colin

Смежные вопросы