在CSV文件中查找主键候选者的最快方法?

时间:2018-06-20 09:31:51

标签: python pandas primary-key

在CSV文件中查找候选主键,该测试要求列<= 4的数字,并且这些列的任何子集都不能作为主键。 有一些例子:

  • key1:[column18] ==>正确
  • 密钥2:[column15,column18] ==>错误,包含密钥1
  • 键3:[column1,column2,column3,column5,column6] ==>错误,大于4

这是对我的考验!我已经完成了 35秒,但是没有达到有效的要求:在大约10000行的csv文件中,在大约 1秒的时间内找到了主键。

测试数据:

所以,我的问题是“有没有更有效的方法来查找主键?”

这是我的代码:

# 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)))

我发现有一个类似的问题,How to find a columns set for a primary key candidate in CSV file?,但是代码效率不高,并找到了错误的密钥,例如示例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)))

2 个答案:

答案 0 :(得分:1)

以下是一些想法:

  • 计算每列的唯一值的数量。将这些数字称为u_1,u_2,...。删除/跳过组合,将这些数字相乘会导致乘积<文件中的行数。在您的示例文件中,这将有效地删除几乎所有组合,因为只有布尔列最多可以标识2 * 2 * 2 * 2 = 16个不同的值。
  • 请勿将候选人转换为集合。您只需要检查子序列,而不是子集。线性地执行此操作而不创建新的数据结构。复制内存非常昂贵。设置相交操作也很昂贵。
  • 使用cProfile配置代码。像这样的东西:python3 -m cProfile -s cumtime key-extractifyer.py < lotsadata.csv
  • 重新考虑是否真的需要numpy来解决此问题。对于大多数操作,Python列表已经具有固定时间或摊销的固定时间运行时间。

要阐明第一点:

让我们从抽象的角度开始。您正在尝试找到满足某些约束的列组合。在这种问题中,我们将所有候选项称为“ search space”,每个candidate that satisfy the constraints都称为“问题的解决方案”。现在,您的搜索空间是“ itertools.combinations生成的所有组合”。您正在检查对每个候选人的约束。

现在让我们假设检查约束条件是昂贵的。在您的情况下,这是pandas.duplicated()调用,占执行时间的95%。显然,如果可以最大程度地减少搜索空间,我们可以节省很多时间!

可以通过三种方式来最小化搜索空间:

  1. 找到一种生成候选的算法,该算法产生较少的误报。如果您确实需要,我将保留此练习。
  2. 找到更快的方法来排除不符合约束条件的候选者。这是我的第一点。
  3. 找到一些隐藏的结构来解决这个问题,该问题使您可以同时排除大部分搜索空间。示例:如果列A,B,C,D不是主键,则还可以排除这些列的所有较短组合。您也许可以找到一个数据结构,使您可以执行这种“清除清除”操作。

我专注于“方法2”,但如果需要,也可以随时探索其他方法。

搜索空间的很大一部分是带有二进制值的列。除此之外,您还有一些只包含单个值的列。出于数学原因,仅涉及这些列的组合永远无法满足您的约束(此类组合绝不能解决您的问题)。可以包含2个值的一列最多可以唯一地标识2行。如果您有3行或更多行,则必须至少有两行在该列中共享相同的值。这称为pigeonhole principle

我们可以将其扩展到更多列。两个二进制列最多可以标识2 * 2 = 4行,并具有以下组合:(0, 0), (0, 1), (1, 0), (1, 1)。并缩放到大于(或小于)两个值的列。具有3个唯一值的两列最多可以标识3 * 3 = 9行。

这是我们要利用的原理,以减少搜索空间。通过在开始时计算每列中 unique / distinct 值的数量,并将结果保存到数组中,这样就不必在每次循环迭代时都这样做,您可以给出一个上限该列将能够识别多少行-AT MOST。因此,在执行昂贵的pandas.duplicated()调用之前,请检查较便宜的乘法,以查看此列组合是否有可能满足约束条件。如果不是这样,我们可以避免昂贵的电话。请注意,我们并不是在试图证明您的候选人,而是在试图证明您的候选人不是。在确定之前,您仍然需要打出昂贵的电话,但是候选人的数量要少得多。

答案 1 :(得分: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]
...