【问题标题】:How should I filter large (70+MB) text files of CSVs with multiple filter types我应该如何使用多种过滤器类型过滤 CSV 的大 (70+MB) 文本文件
【发布时间】:2019-02-22 23:38:38
【问题描述】:

tl;dr:我想在 600k 行 csv 中使用 3 个不同过滤器匹配的 col 条目更改列值,该怎么做?

我有几个超过 600,000 行的数据文件。它们看起来像这样:

random.website.com|1000002644|FunGRP_1000009280_OT|5556667777@random.website.com|User|5556667777|main|Y|6557|main||6557|

我想在匹配时将第 8 列的值 Y/N 更改为 N

我有一个存储在换行符分隔的文本文件中的第 2 列(企业)第 3 列(组)和第 6 列(电话号码)的过滤器列表,如下所示:

电话号码

5553690049
5553690050
5553690052
...

企业

Loud-YPOxXTFF
res-http
1700000004
...

FunGRP_1000009280_OT
1300000004_CollabGrp_1
HostedVKL_1300000035_SA
...

现在我有一个 bash 程序,它遍历数据中的每个条目,提取我想用 awk 过滤的列(这意味着 1800k awk 调用),然后在每个要检查的内容上循环三次,然后读取每个循环过滤,然后检查过滤器是否与项目匹配。如果是,则 awk 行(第 4 awk)以替换第 8 列并将其填充到输出文件中。如果没有过滤器与该行匹配,则只需将未更改的行放在输出文件中。这是非常低效的,但它确实有效。代码如下:

filter () {
  while read -r entry || [[ -n "$entry" ]]; do
    phone="$(echo "$entry" | awk -F "|" '{ print $6 }')"
    group="$(echo "$entry" | awk -F "|" '{ print $3 }')"
    enterprise="$(echo "$entry" | awk -F "|" '{ print $2 }')"
    to_test=("$phone" "$group" "$enterprise")
    filters=("$NUMBER_FILTER_FILE" "$GROUP_FILTER_FILE" "$ENTERPRISE_FILTER_FILE")
    count=-1
    matched=""

    for item in "${to_test[@]}"; do
      count=$(( count+1 ))
      if [[ -n "$item" ]] && [[ -f "${filters[$count]}" ]]; then
        while read -r filter || [[ -n "$filter" ]]; do
          if [[ "$item" = "$filter" ]]; then
            echo "$entry" | awk -F "|" 'BEGIN {OFS = FS} $8="N" {print}' >> "$WORKING$OUTPUTFILE"
            matched="true"
            continue 2
          fi
        done < "${filters[$count]}"
      fi
    done

    # If no filter matches, put the original entry in the output
    [[ -z "$matched" ]] && echo "$entry" >> "$WORKING$OUTPUTFILE"
  done < "$WORKING$UNFILTEREDOUTPUTFILE"
}

我需要这样更有效率,我觉得在 bash 中这样做很愚蠢,这就是我在这里标记 python 的原因。我对python很熟悉。

我已经打算通过将 awk 调用移出循环来捕获每一整列来改进它。像PHONENUM_COL=($(awk '{FS = "|"} {print $6}' data.txt)) 这样的东西。然后(假设它们最终的长度相同)我可以循环遍历数组的长度并匹配如下内容:

[[ "PHONE_COL[$COUNT]" = "$filter" | "GROUP_COL[$COUNT]" = "$filter" | "ENTERPRISE_COL[$COUNT]" = "$filter" ]]

我正在更新的原始程序是用 bash 编写的,这就是为什么我继续尝试在 shell 脚本中解决这个问题,但我不是 bash 的向导,所以我开始研究 python + pandas这样做是因为我觉得这应该更容易。任何建议、策略或想法都会有所帮助。谢谢。

【问题讨论】:

  • 如果我理解正确,ith 行是否匹配其电话号码、组或企业是否与这些文件中的ith 条目匹配?不是说它匹配任何文件中的任何条目,对吧?
  • 是的,如果任何过滤器与所考虑的 3 个相应术语之一匹配,则该行匹配,我们将 $8 切换为 N

标签: python bash


【解决方案1】:

如果我理解正确,以下将起作用:

awk "
BEGIN {FS = OFS = \"|\"}
FILENAME=="\"$NUMBER_FILTER_FILE\"" {phone[\$0]++; next}
FILENAME=="\"$GROUP_FILTER_FILE\"" {group[\$0]++; next}
FILENAME=="\"$ENTERPRISE_FILTER_FILE\"" {enterprise[\$0]++; next}
FILENAME=="\"$WORKING$UNFILTEREDOUTPUTFILE\"" {
    if (phone[\$6] || group[\$3] || enterprise[\$2]) \$8 = \"N\"
    print
}" "$NUMBER_FILTER_FILE" "$GROUP_FILTER_FILE" "$ENTERPRISE_FILTER_FILE" "$WORKING$UNFILTEREDOUTPUTFILE" > "$WORKING$OUTPUTFILE"

恐怕您发布的示例没有提供足够的信息 (例如,1000002644 行的第二个字段与任何行都不匹配 在Enterprises) 中,我做了一些假设。 如果我的代码不能正常工作,如果你能发布我将不胜感激 更多信息来检查我的代码。 BR。

[解释]
关键是我们如何减少计算次数。你的原创 代码重复扫描过滤器文件中的关键字 主循环,冗余且低效。我们可以大幅度 通过在awk 中使用associative arrays 来减少冗余。 (请注意,许多语言都实现了相同的机制 不同的名称:Perl 中的hashPython 中的dictionary 等)

让我用第一行 FunGRP_1000009280_OT 来说明 $GROUP_FILTER_FILE。通过使用关联数组在单词上标记 通过说group["FunGRP_1000009280_OT"]++,我们可以后记测试是否 该单词以最小的计算成本包含在列表中。

现在让我们回到我的代码。只是为了使用shell 变量为$NUMBER_FILTER_FILE 等。我附上了awk 脚本 用双引号,而不是单引号。它可能不是标准方案,因为 它需要大量的反斜杠转义。 (我可能应该通过 通过-v 选项的shell 变量。)

  • BEGIN 块在读取输入行之前只执行一次,然后 我已将 | 分配给输入和输出字段分隔符。
  • 其余代码在文件中的每个输入行上执行 指定为命令行参数。
  • 模式FILENAME=="$NUMBER_FILTER_FILE" 测试当前是否 输入文件是$NUMBER_FILTER_FILE 和下面的块{...} 如果模式匹配,则执行。
  • 输入行中的单词自动分配给awk 变量$0。然后$NUMBER_FILTER_FILE 的第一行作为 phone["5553690049"]++ 如上所述在上面做标记。
  • 以下代码根据内容设置每个关联数组 每个文件。
  • FILENAME=="$WORKING$UNFILTEREDOUTPUTFILE" 开头的最后一行 是迭代 csv 文件的主循环。这条线被分解为 由|分隔的字段,并且这些字段分配给$1$2,... 按顺序排列。
  • 如果if (...) 测试为真,则第8 个字段$8 设置为"N"

希望这会有所帮助。

【讨论】:

  • 这就像一个魅力。我必须做一些阅读才能理解它,因为我不是一个伟大的 AWKer,但现在我明白了你是如何做到的,我觉得我学到了很多东西。解决方案和学习都值得,谢谢!
  • 很高兴知道它正在工作。我应该解释它是如何工作的。我已经用一些解释更新了我的答案。希望对您有所帮助。
【解决方案2】:

这是一个 Python/Pandas 版本,它应该相当快并且可读。

import pandas as pd

# Load all the data
data = pd.read_csv('input.csv', sep="|", names=['site', 'entreprise', 'group', 'mail', 'name', 'phone', 'a', 'yn', 'b', 'c', 'd', 'e', 'f'])
phones = pd.read_csv('phones.dat', header=None)
entreprises = pd.read_csv('entreprises.dat')
groups = pd.read_csv('groups.dat')

# Define the match function
def match(row):
    return row['phone'] in phones.values or row['entreprise'] in entreprises.values or row['group'] in groups.values

# Update the column with match function
data['yn'] = data.apply(match, axis=1)

# Write output
data.to_csv('output.csv', sep="|", header=False, index=False)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-15
    • 1970-01-01
    • 2018-04-18
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 2016-04-09
    • 2012-08-22
    相关资源
    最近更新 更多