Обнаружение всех изменений в датафрейме - PullRequest
1 голос
/ 15 января 2020

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

Попытка наивного решения:

import pandas as pd 


# Initalize the data
data_original = [['4', 'NYC','New York'], ['3', 'BOS','Boston'], ['2', 'CHI','Chicago']]
data_new = [['4', 'NYC','New York','50'], ['3', 'Boston','Boston','100'], ['2', 'CHI','Chicago','20'], ['8', 'LA','Los Angeles','30']] 

# Create the dataframes
df_original = pd.DataFrame(data_original, columns = ['Office Number', 'Office Name','Office Location'])
df_new = pd.DataFrame(data_new, columns = ['Office Number', 'Office Name','Office Location','Money'])

df_changes = df_new[ ~df_new.isin(df_original)].dropna()

Результирующий фрейм данных:

['8', 'LA','Los Angeles','30']

Это наивное решение не то, что я ищу, потому что оно не обнаруживает изменение «BOS» на «Boston» во втором элементе фрейма данных. Я ищу что-то, что указывало бы, даже если один элемент в строке был изменен. Сложение или вычитание столбцов я могу выяснить отдельно, но как можно обнаружить поэлементные изменения, такие как «Бостон» в «Бостон»?

Ответы [ 3 ]

3 голосов
/ 15 января 2020

Комплексная проверка

def compare(old, new):
    new_cols = new.columns.difference(old.columns)
    del_cols = old.columns.difference(new.columns)
    new_indx = new.index.difference(old.index)
    del_indx = old.index.difference(new.index)

    # Now that we've checked new and deleted rows and columns
    # `align` the dataframes and check the values
    old, new = old.align(new, 'inner')

    I, J = np.where(old.ne(new))
    c = old.columns
    r = old.index

    changes = pd.DataFrame([
        [r[i], c[j], old.iat[i, j], new.iat[i, j]]
        for i, j in zip(I, J)
    ], columns=['Row', 'Column', 'Old', 'New'])

    return changes, new_cols, del_cols, new_indx, del_indx

Демонстрация

Получить данные изменения

changes, new_cols, del_cols, new_indx, del_indx = compare(df_original, df_new)

Распечатать хороший отчет

print(f"""\
New Columns:
{' '.join(new_cols.astype(str))}

Deleted Columns:
{' '.join(del_cols.astype(str))}

New Rows:
{' '.join(new_indx.astype(str))}

Deleted Rows:
{' '.join(del_indx.astype(str))}

Changes:
{changes}
""")

New Columns:
Money

Deleted Columns:


New Rows:
3

Deleted Rows:


Changes:
   Row       Column  Old     New
0    1  Office Name  BOS  Boston

_______________________________________________________

Более простое решение

Мы можем за go разбить поиск добавленных и удаленных столбцов и строк и просто правильно интерпретировать нулевые значения в changes кадре данных.

def compare(old, new):
    old, new = old.align(new)  # Notice I don't use `'inner'` as I did before

    I, J = np.where(old.ne(new))
    c = old.columns
    r = old.index

    changes = pd.DataFrame([
        [r[i], c[j], old.iat[i, j], new.iat[i, j]]
        for i, j in zip(I, J)
    ], columns=['Row', 'Column', 'Old', 'New'])

    return changes

compare(df_original, df_new)

   Row           Column  Old          New
0    0            Money  NaN           50
1    1            Money  NaN          100
2    1      Office Name  BOS       Boston
3    2            Money  NaN           20
4    3            Money  NaN           30
5    3  Office Location  NaN  Los Angeles
6    3      Office Name  NaN           LA
7    3    Office Number  NaN            8

В этом случае единственное изменение представлено ненулевым значением в столбце 'Old'. Все остальные являются новыми.

_______________________________________________________

Более безопасное решение

В случае, если у вас есть np.nan как в новом, так и в старом фреймах данных, это будет оцениваться как не равное , Эта версия учитывает это.

Тем не менее, он все равно не поймает, если один кадр данных имеет None, а другой - np.nan. Я оставлю это как упражнение для будущих читателей.

def compare(old, new):
    old, new = old.align(new)

    I, J = np.where(old.ne(new))
    c = old.columns
    r = old.index

    data = []
    for i, j in zip(I, J):
        n = new.iat[i, j]
        o = old.iat[i, j]
        if pd.notna(n) or pd.notna(o):
            data.append([r[i], c[j], o, n])

    return pd.DataFrame(data, columns=['Row', 'Column', 'Old', 'New'])
1 голос
/ 15 января 2020

Если сравнение идет по индексам , то нам нужно два reindexlike вызова. Первый позволяет сравнивать оригинал с новым DataFrame, независимо от дополнительных строк или столбцов. Вторая помечает все дополнительные строки и столбцы как True. Результирующий DataFrame равен True, где df_new отличается от df_original.

m = (df_new.reindex_like(df_original)
           .ne(df_original)
           .reindex_like(df_new)
           .fillna(True))

   Office Number  Office Name  Office Location  Money
0          False        False            False   True
1          False         True            False   True
2          False        False            False   True
3           True         True             True   True

# Can slice to see changes
df_new[m]
  Office Number Office Name Office Location Money
0           NaN         NaN             NaN    50
1           NaN      Boston             NaN   100
2           NaN         NaN             NaN    20
3             8          LA     Los Angeles    30
1 голос
/ 15 января 2020

Может быть, это то, что вам нужно?

df_changes = df_new[ ~df_new[["Office Number","Office Name","Office Location"]].apply(tuple,1).isin(df_original[["Office Number","Office Name","Office Location"]].apply(tuple,1))].dropna()

Не знаю, нужен ли сейчас дропна (). Вы можете установить столбцы как кортеж, чтобы он был неизменным, и вы сравниваете по ключам. Проверено с вашими данными испытаний, и это работает, я думаю.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...