Найти кандидатов первичного ключа в CSV файле, для проверки требуется число столбцов <= 4, и любое подмножество этих столбцов не может быть первичным ключом. Есть пример:
Это испытание для меня! Я закончил его за 35 секунд, но не достиг необходимого эффективного требования: найдите первичные ключи примерно за 1 секунду в файле csv около 10000 строк.
Данные испытаний:
Правильный ответ этого файла: [['column8', 'column11', 'column15', 'column18']]
Итак, мой вопрос: "Есть ли более эффективный способ найти первичные ключи?"
Вот мой код:
# 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)))
Вот несколько идей:
python3 -m cProfile -s cumtime key-extractifyer.py < lotsadata.csv
Чтобы прояснить первый пункт:
Начнем с абстрактного уровня. Вы пытаетесь найти комбинации столбцов, которые удовлетворяют некоторому ограничению. В этом виде мы называем всех кандидатов " поисковым пространством ", и каждый кандидат, который удовлетворяет ограничениям, называется "решением проблемы". Прямо сейчас ваше пространство поиска - это "все комбинации, созданные с помощью itertools.combinations". Вы проверяете свои ограничения по отношению к каждому кандидату.
Предположим теперь, что проверка ограничений стоит дорого. В вашем случае это pandas.duplicated()
, который составляет 95% от вашего времени выполнения. Ясно, что мы сможем сэкономить много времени, если мы сможем минимизировать пространство для поиска!
Минимизировать пространство поиска можно тремя способами:
Я сосредотачиваюсь на "пути 2", но не стесняйтесь исследовать другие способы, если вам нужно.
Огромная часть вашего пространства поиска - это столбцы с двоичными значениями. В дополнение к этому у вас есть несколько столбцов с одним значением в них. Для математических соображений комбинации, которые используют ТОЛЬКО эти столбцы, никогда не смогут удовлетворить ваши ограничения (такие комбинации никогда не будут решением вашей проблемы). Один столбец, который может иметь 2 значения, в лучшем случае сможет однозначно идентифицировать 2 строки. Если у вас 3 или более строк, должно быть не менее двух строк, которые имеют одинаковое значение в столбце. Это называется принципом голубины.
Мы можем масштабировать это до большего количества столбцов. Два бинарных столбца могут наиболее точно идентифицировать 2 * 2 = 4 строки, причем эти комбинации: (0, 0), (0, 1), (1, 0), (1, 1)
. И он масштабируется до столбцов с большим количеством (или меньше) значений. Два столбца с 3 уникальными значениями в каждом могут в наибольшей степени идентифицировать 3 * 3 = 9 строк.
Это принцип, который мы хотим использовать для сокращения пространства поиска. Считая количество уникальных/отдельных значений в каждом столбце в начале и сохраняя результаты в массиве, поэтому вам не нужно делать это на каждой итерации цикла, вы можете дать верхнюю оценку того, сколько строк, в столбце быть в состоянии определить - НА САМОМ. Поэтому, прежде чем вы выполните дорогой pandas.duplicated()
, вы проверите более дешевое умножение, чтобы увидеть, есть ли вероятность, что эта комбинация столбцов сможет удовлетворить ограничения. Если это не так, мы можем избежать дорогого вызова. Обратите внимание, что мы не пытаемся доказать, что ваш кандидат является решением, мы пытаемся доказать, что это не так. Вам все равно придется делать дорогой звонок, прежде чем вы знаете наверняка, но на значительно меньшем количестве кандидатов.
Я все еще не уверен, что я четко понимаю проблему, но следующие карты каждый номер строки в список индексов столбцов, с помощью которых он может быть однозначно идентифицирован, и работает в ~ 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]
...
pandas.duplicated()
, 37 с / (всего 39 с), аnumpy
равен 1,7 с. Поэтому я подумал о реализации вашего первого предложения, но я не совсем понимаю этот момент. Можете ли вы объяснить первый пункт в деталях? Спасибо!