Эффективное сопоставление данных
Рассмотрим простой способ поиска закономерностей между двумя последовательностями. Его применение значительно упростит качественную обработку данных и позволит не потерять время на неэффективные промежуточные решения.
Для наглядности будем работать с игрушечными данными о заработке людей из разных городов (идентифицируются по индексу) и суммы помощи им из местного бюджета (для каждого города фиксированная сумма) следующего вида (код приведен в конце статьи):
Допустим, при обработке значений мы опираемся на гипотезу об однозначном определении человека по ФИО. Для проверки этого можно воспользоваться методом duplicated:
data_m[data_m.duplicated(subset=['ФИО'])]
В результате, получены 2300 строк, которые являются повторными для уже имеющихся, при это соответствующие им пары не включены. Для вывода всех значений можно задать параметр keep=False (по умолчанию первое значение из цепочки повторов как дубликат не помечается):
Вот в такой ситуации нас может интересовать, каким городам соответствует один и тот же человек. То есть устанавливаем соотношение один ко многим. Это можно осуществить очевидным способом, выбирая дублирующие строки для одного значения и устанавливая соответствие с такими же строками в другой последовательности, где значения не являются повторными. Однако еще проще осуществить группировку с помощью следующей функции:
def get_diffs_one_many(data, col, col_gr, col_many): def diffs(df, col_many): d = df.drop_duplicates(subset=[col_many]) if d.shape[0] > 1: return d df = data.copy() df[col_gr] = col res = df.groupby(col_gr).apply(lambda df: diffs(df, col_many)).reset_index(drop=True) return res
Она получает датафрейм, столбец для группировки, имя которое ему присваивается и имя столбца с множеством значений. Внутри нее реализована функция, отбирающая строки, в которых встречается больше одного значения второй последовательности, соответствующего конкретному значению первой. Вызовем функцию для нашего примера:
fio_town = general_modules.df_check_info.get_diffs_one_many( data_m, data_m['ФИО'],'ФИО', 'индекс_города')
Можно эту же функцию использовать и для определения обратного соотношения - "город - люди".
Дополнительную пользу функция get_diffs_one_many может принести при проверке качества разметки некого поля. Например, вы сформировали критерий разметки, разметили, а затем выводите список различных значений поля соответствующих одному и тому же имени.
Код для генерации использованного выше набора приведен ниже:
import pandas as pd
import numpy as np
import string
np.random.seed(0)
letters = np.array(list(string.ascii_uppercase))
digits = np.arange(0,10)
people_num = 10000
towns_num = 200 # количество городов, нацело делит people_num для удобства
dirn = 'people_income'
# людям соответствуют по три случайные буквы алфавита (имя, фамилия)
names = np.random.choice(letters, size=(people_num,3))
# зарплата от 20 до 50 тыс. рублей, случайное равномерное распределение
salary = np.random.uniform(20,50, size=people_num)
# индекс города - по семь цифр из алфавита, их меньше чем людей,
# так как предполагаем одинаковые города для разных людей
loc_ind_p = np.random.choice(digits, size=(towns_num,7))
loc_ind = np.tile(loc_ind_p, (people_num//towns_num,1))
# склеим буквы имен и фамилий, а также
# индексы городов
names = pd.DataFrame(names, dtype='str').apply(lambda x:' '.join(x),axis=1)
loc_ind = pd.DataFrame(loc_ind, dtype='str').apply(lambda x:''.join(x),axis=1)
data = pd.DataFrame({'ФИО':names, 'индекс_города':loc_ind,
'зарплата':salary})
data['зарплата'] = np.round(data['зарплата'],2)
helps = np.random.uniform(5,10, size = towns_num)
town_help = pd.DataFrame({'индекс_города':pd.DataFrame(loc_ind_p, dtype='str')
.apply(lambda x:''.join(x), axis=1),
'сумма_помощи':helps})
town_help['сумма_помощи'] = np.round(town_help['сумма_помощи'],2)
data_m = pd.merge(data, town_help, left_on = 'индекс_города',
right_on='индекс_города')
# перемешиваем данные
data_m = data_m.sample(frac=1).reset_index(drop=True)