Самый быстрый способ найти кандидатов первичного ключа в файле CSV?

1

Найти кандидатов первичного ключа в CSV файле, для проверки требуется число столбцов <= 4, и любое подмножество этих столбцов не может быть первичным ключом. Есть пример:

  • key1: [column18] ==> правильный
  • key2: [column15, column18] ==> wrong, содержит key1
  • key3: [column1, column2, column3, column5, column6] ==> неверно, более 4

Это испытание для меня! Я закончил его за 35 секунд, но не достиг необходимого эффективного требования: найдите первичные ключи примерно за 1 секунду в файле csv около 10000 строк.

Данные испытаний:

Итак, мой вопрос: "Есть ли более эффективный способ найти первичные ключи?"

Вот мой код:

# coding: utf-8
import pandas as pd
import numpy as np
import os
import itertools
import time

def is_subkey(newkey,keys):
#     test wheather newkey is sub set of any keys
    for key in keys:
        if set(key).issubset(newkey):
            return True
    return False

def primarykey_recognition(file,max_num=4):
#     file is an file object, returnd by function open()
#   doc is a pandas DF
    doc = pd.read_csv(file,sep=',')
    num = 1
    result = []
    table_length = len(doc.values)
    while num <= max_num:
        keys = list(itertools.combinations(doc.columns,num))
#         print(keys)
        for key in keys:
            if is_subkey(key,result):
#           if key belong to any sub set of keys in result ,continue
                continue
#           
            bools = np.array(doc.duplicated(subset=list(key)))
            if np.sum(bools) > 0:
#           sum(bools) means bools has duplicated lines 
                continue
            else:
                result.append(list(key))
        num += 1
    return result

if __name__=="__main__":

    with open(r"..\data\Table_C.csv") as file:
        tic = time.clock()
        keys = primarykey_recognition(file)
        toc = time.clock()

        print("File {} has primary keys: ".format(filename))
        print(keys)
        print("Elapsed: {} s".format(round(toc - tic,4)))

Я нахожу, что есть аналогичный вопрос: Как найти набор колонок для кандидата первичного ключа в CSV файле? , но код неэффективен и находит неправильные ключи, например, key2.

Вот код этого вопроса:

# coding: utf-8
import pandas
from itertools import chain, combinations
import time

def key_options(items):
    return chain.from_iterable(combinations(items, r) for r in range(1, len(items)+1) )

tic = time.clock()    
df = pandas.read_csv(r"..\data\Table_C.csv");

# iterate over all combos of headings, excluding ID for brevity
for candidate in key_options(list(df)[1:]):
    deduped = df.drop_duplicates(candidate)

    if len(deduped.index) == len(df.index):
        print(','.join(candidate))

toc = time.clock()
print("Elapsed: {} s".format(round(toc - tic,4)))
Теги:
pandas
primary-key

2 ответа

1
Лучший ответ

Вот несколько идей:

  • Подсчитайте количество уникальных значений для каждого столбца. Вызовите эти числа u_1, u_2,... Удалите/пропустите комбинации, где умножение этих чисел приведет к продукту <количество строк в файле. В вашем файле примера это эффективно удалит почти все комбинации, потому что только логические столбцы могут наиболее точно идентифицировать 2 * 2 * 2 * 2 = 16 различных значений.
  • Не конвертируйте своих кандидатов в группы. Вам нужно только проверить подпоследовательности, а не подмножества. Сделайте это линейно, не создавая новые структуры данных. Копирование памяти дорого. Установить операции пересечения также дорого.
  • Профилируйте свой код с помощью cProfile. Что-то вроде этого: python3 -m cProfile -s cumtime key-extractifyer.py < lotsadata.csv
  • Пересмотрите, если вам действительно нужно numpy для этой проблемы. Для большинства операций списки Python имеют постоянную или амортизированную продолжительность времени выполнения.

Чтобы прояснить первый пункт:

Начнем с абстрактного уровня. Вы пытаетесь найти комбинации столбцов, которые удовлетворяют некоторому ограничению. В этом виде мы называем всех кандидатов " поисковым пространством ", и каждый кандидат, который удовлетворяет ограничениям, называется "решением проблемы". Прямо сейчас ваше пространство поиска - это "все комбинации, созданные с помощью itertools.combinations". Вы проверяете свои ограничения по отношению к каждому кандидату.

Предположим теперь, что проверка ограничений стоит дорого. В вашем случае это pandas.duplicated(), который составляет 95% от вашего времени выполнения. Ясно, что мы сможем сэкономить много времени, если мы сможем минимизировать пространство для поиска!

Минимизировать пространство поиска можно тремя способами:

  1. Найдите алгоритм для создания кандидатов, который дает меньше ложных срабатываний. Я оставлю это как упражнение, если вам действительно нужно.
  2. Найдите более быстрые способы исключения кандидатов, которые не удовлетворяют ограничениям. Это то, что мой первый пункт делает.
  3. Найдите скрытую структуру проблемы, которая позволяет одновременно исключать большие части пространства поиска. Пример: если столбцы A, B, C, D не являются первичным ключом, вы также можете исключить все более короткие комбинации этих столбцов. Возможно, вам удастся найти структуру данных, которая позволит вам выполнять такие "радикальные удаления".

Я сосредотачиваюсь на "пути 2", но не стесняйтесь исследовать другие способы, если вам нужно.

Огромная часть вашего пространства поиска - это столбцы с двоичными значениями. В дополнение к этому у вас есть несколько столбцов с одним значением в них. Для математических соображений комбинации, которые используют ТОЛЬКО эти столбцы, никогда не смогут удовлетворить ваши ограничения (такие комбинации никогда не будут решением вашей проблемы). Один столбец, который может иметь 2 значения, в лучшем случае сможет однозначно идентифицировать 2 строки. Если у вас 3 или более строк, должно быть не менее двух строк, которые имеют одинаковое значение в столбце. Это называется принципом голубины.

Мы можем масштабировать это до большего количества столбцов. Два бинарных столбца могут наиболее точно идентифицировать 2 * 2 = 4 строки, причем эти комбинации: (0, 0), (0, 1), (1, 0), (1, 1). И он масштабируется до столбцов с большим количеством (или меньше) значений. Два столбца с 3 уникальными значениями в каждом могут в наибольшей степени идентифицировать 3 * 3 = 9 строк.

Это принцип, который мы хотим использовать для сокращения пространства поиска. Считая количество уникальных/отдельных значений в каждом столбце в начале и сохраняя результаты в массиве, поэтому вам не нужно делать это на каждой итерации цикла, вы можете дать верхнюю оценку того, сколько строк, в столбце быть в состоянии определить - НА САМОМ. Поэтому, прежде чем вы выполните дорогой pandas.duplicated(), вы проверите более дешевое умножение, чтобы увидеть, есть ли вероятность, что эта комбинация столбцов сможет удовлетворить ограничения. Если это не так, мы можем избежать дорогого вызова. Обратите внимание, что мы не пытаемся доказать, что ваш кандидат является решением, мы пытаемся доказать, что это не так. Вам все равно придется делать дорогой звонок, прежде чем вы знаете наверняка, но на значительно меньшем количестве кандидатов.

  • 0
    Согласно вашему третьему предложению, я протестировал время выполнения файла .py. Я обнаружил, что наиболее трудоемким является pandas.duplicated() , 37 с / (всего 39 с), а numpy равен 1,7 с. Поэтому я подумал о реализации вашего первого предложения, но я не совсем понимаю этот момент. Можете ли вы объяснить первый пункт в деталях? Спасибо!
  • 0
    @ LIKe Я обновил свой ответ.
0

Я все еще не уверен, что я четко понимаю проблему, но следующие карты каждый номер строки в список индексов столбцов, с помощью которых он может быть однозначно идентифицирован, и работает в ~ 0m0.145s:

from collections import defaultdict
from re import findall
from itertools import combinations

def nest():
  return defaultdict(nest)

d = nest()
row_to_key = nest()

with open('table.csv') as f:
  for idx, line in enumerate(f):
    indices = [idx for idx, i in enumerate(findall(r'[^,]+', line)) \
      if i == 'TRUE']
    for j, k, l, m in combinations(indices, 4):
      if not d[j][k][l][m]:
        d[j][k][l][m] = idx
        row_to_key[idx] = [j, k, l, m]
      break

for row_idx in row_to_key:
  print(' * row number', row_idx, 'can be identified by cols', row_to_key[row_idx])

Это приведет к выводу:

 * row number 1 can be identified by cols [2, 5, 7, 10]
 * row number 2 can be identified by cols [0, 9, 10, 12]
 * row number 3 can be identified by cols [3, 4, 5, 9]
 * row number 4 can be identified by cols [0, 4, 5, 7]
...
  • 1
    о, я понял вашу точку зрения но я виноват в том, что ты неправильно понял первичный ключ. Вы идентифицируете каждую строку с уникальными комбинациями столбцов. Отличная работа! Но первичные ключи, которые мне нужны, должны быть из тех же столбцов. В любом случае, ваш ответ очень вдохновляет, еще раз спасибо!
  • 0
    Хм, мне все еще неясно, что вы ищете (составной первичный ключ?), Но похоже, у вас есть путь вперед, так что удачи

Ещё вопросы

Сообщество Overcoder
Наверх
Меню