【问题标题】:(bash/Linux) most efficient way to subset rows and columns based on row and column indices in other files(bash/Linux) 根据其他文件中的行和列索引对行和列进行子集化的最有效方法
【发布时间】:2022-02-07 18:06:19
【问题描述】:

我有一个包含数百万行和数千列的数据文件。我需要某些行和某些列的数据。我有一个包含我需要的行号的文本文件,以及另一个包含我需要的列标题的文本文件。我必须像这样对几百个数据文件进行子集化,所以我试图弄清楚如何快速完成它,而不会创建巨大的中间文件。

我在 bash 脚本方面不是很有经验。到目前为止,我最好的方法是使用 awk 提取必要的行来创建一个中间文件,并将结果通过管道传输到 cut 以便输出文件只包含必要的列:

COLS=($(zcat big_zipped_file.txt.gz | head -n 1 | tr '\t' '\n' | grep -nf column_headers.txt | sed 's/:.*$//'))

awk 'NR==FNR{a[$0];next} FNR in a' line_numbers.txt <(zcat big_zipped_file.txt.gz) | 
  cut -f 1$(printf ",%s" "${COLS[@]}") >> output_file

有没有一种方法可以更快,并且不会创建大型临时文件?

【问题讨论】:

  • 考虑查看How to create a minimal, reproducible example?,然后回来更新问题;特别是包含行和列标题数据的示例文件,具有 5-10 行和列的输入文件(应该包括要保留的行和列以及要忽略的一些),代码生成的(错误)输出,和(正确的)预期输出
  • csvkit 包中的 csvcut 通常可以方便地按名称提取列。
  • markp-fuso:感谢您的回复。该问题适用于任何具有行和列的大文件,因此我故意省略了示例文件,因为它们会造成不必要的混乱。也许我应该澄清一下我已经得到了正确的输出。问题是:有没有更有效的获取方式?
  • 列文件和行文件是所有数据文件通用的吗?编号为1 的行对应什么:标题或标题后的第一行数据?

标签: linux bash performance memory subset


【解决方案1】:

解决方法是对每个文件扫描一次。

我们使用gawk标准Linux awk脚本。

读取前 2 个文件是 columns.txt 和 rows.txt(顺序不重要)。

一个接一个地读取1个或多个数据文件,没有任何临时文件。

输入文件

rows.txt

1
7
5
2

columns.txt

col5
col3
col2

数据.txt

col1 col2 col3 col4 col5
11   12    13    14   15
21   22    23    24   25
31   32    33    34   35
41   42    43    44   45
51   52    53    54   55
61   62    63    64   65
71   72    73    74   75

script.awk

BEGIN {
    rowsNumbersFileName="rows.txt";
    clumnNamesFileName="columns.txt";
}

ENDFILE { # mark data files when inputFilesCount > 1
    ++inputFilesCount;
 }
 
FILENAME == rowsNumbersFileName { # read rows number
    rowsArr[$0] = ++rowsCount;
}

FILENAME == clumnNamesFileName { # read desired column headers
    columnHeadersArr[$0] = ++columnHeadersCount;
}

inputFilesCount > 1 && FNR == 1 { # caluculate column headers markers
    OFS = "";
    for (i = 1; i <= NF; i++) { # scan all header names
        if ($i in columnHeadersArr) { # if current field is in columnHeadersArr
            columnNumbersArr[++columnNumbersCount] = i; # collect field name in columnNumbersArr
            printf ("%s   %s", OFS, $i); # Ouput current field as header
            OFS = ","; 
        }
    }
    printf("\n");
    next;
}

inputFilesCount > 1  && (FNR - 1) in rowsArr {
    OFS = "";
     for (currColumnNum in columnNumbersArr) {
        printf ("%s     %s", OFS, $columnNumbersArr[currColumnNum]);
        OFS = ",";
    }
    printf("\n");
}

输出

awk -f script.awk rows.txt columns.txt data.txt
   col2,   col3,   col5
     12,     13,     15
     22,     23,     25
     52,     53,     55
     72,     73,     75

【讨论】:

    猜你喜欢
    • 2016-01-14
    • 1970-01-01
    • 1970-01-01
    • 2014-05-07
    • 2016-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多