April 25, 2021

Эффективное сопоставление данных

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

Для наглядности будем работать с игрушечными данными о заработке людей из разных городов (идентифицируются по индексу) и суммы помощи им из местного бюджета (для каждого города фиксированная сумма) следующего вида (код приведен в конце статьи):

Допустим, при обработке значений мы опираемся на гипотезу об однозначном определении человека по ФИО. Для проверки этого можно воспользоваться методом 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)