【问题标题】:Copy rows from CSV based on column value; then split into separate shuffled CSVs根据列值从 CSV 复制行;然后拆分成单独的洗牌 CSV
【发布时间】:2026-01-05 20:35:01
【问题描述】:

sh noob 所以要温柔。这是一个使用命令行的预处理练习,(我在 mac 上)。

我有一个大的 CSV 文件 (original.csv) ~1M 行,4 列。我想创建基于列值提取所有行的处理脚本,获取所有不同的行。第 1 列中有 138393 个不同的值。我通过 awk 执行上述操作。

从这里我想取这些找到的值的一半,随机排列(或随机选择)然后将两组分成两个 CSV 文件(file1.csvfile2.csv)。 FWIW 它用于机器学习练习,因此将数据拆分为测试/训练。

什么是执行此操作的有效方法?我现在拥有的最大瓶颈,(可能更多我没有看到):

  1. 通过awk搜索列值匹配
  2. IO 成本从复制行到单独的 csv,然后遍历每个 csv + 将一半值附加到 train.csvtest.csv
  3. 洗牌上面的每个文件

...奖励:任何加速整个过程的多线程解决方案!

我的 CSV 数据是基本数据(并且已经按第 1 列值排序):

1,2,3.5,1112486027
1,29,3.5,1112484676
1,32,3.5,1112484819
1,47,3.5,1112484727

代码:

#!/bin/bash


DATA_FILE=noheader.csv
awk -F "," '{ print >> ("r"$1".csv"); close("r"$1".csv") }' $DATA_FILE          # Creates seperate CSV file for each userID

ID_FILE=unique_ids.txt
if [ -e $ID_FILE ]
then
    IDX=$(wc -l unique_ids.txt | awk '{print $1}')                              # Get count of total rows in CSV 
    printf "Found %d userIDs \n" $IDX
else
   printf "File %s Not Found! \n" "$ID_FILE"
   printf "Creating Unique IDs File \n"
   cut -d , -f1 $DATA_FILE | sort | uniq > unique_ids.txt
fi

COUNT=0
START=$(date +%s)
for ((i=1; i <= $IDX; i++))                                                     # Iterate through each user CSV file 
{
    FILE=r${i}.csv

    TOT_LNO=$(wc -l $FILE  | awk -v FILE="$FILE" '{ print $1; close(FILE) }')   # Calc total number of rows in file
    SPLT_NO=$(($TOT_LNO / 2))                                                   # ~50% split of user row count for test/train split

    gshuf -n $TOT_LNO $FILE                                                     # Randomly shuffle rows in csv file

    head -n $SPLT_NO $FILE >> train_data.csv
    OFFSET=$(($SPLT_NO + 1))                                                    # Appends first line# rows of user{n} ratings to training data
    tail -n +$OFFSET $FILE >> test_data.csv                                     # Appends rows nums > line# of user{n} ratings to test data

    # awk 'FNR==NR{a[$1];next}($1 in a){print}' file2 file1                     # Prints out similarities btwn files (make sure not train/test splipapge)
    rm $FILE                                                                    # Deletes temp user rating files before proceding

    ((COUNT++))
    if ! ((COUNT % 10000))
        then
        printf "processed %d files!\n" $COUNT
    fi
}

END=$(date +%s)
TIME=$((END-START))
printf "processing runtime: %d:\n" $TIME

输出(假设它被洗牌):

train.csv 
1,2,3.5,1112486027
1,47,3.5,1112484727

test.csv
1,32,3.5,1112484819
1,29,3.5,1112484676

【问题讨论】:

  • 请多解释一下有大约一百万行,而不是 "138393 个不同的值在第 1 列"。目前还不清楚这两个输出文件应该是每个 500K 行,还是每个大约 69196 行。

标签: bash shell csv awk


【解决方案1】:

下面这个方法比the accepted awk answer稍微快一点。

使用shufGNU split-n 选项和mv

grep '^1,' noheader.csv | shuf | split -n r/2 ; mv xaa train.csv ; mv xab test.csv

这对 Mac 不起作用,因为它们使用没有 -n 选项的 BSD split

【讨论】:

  • 这个答案不符合 OP 的要求:I'd like to create processing script that pulls all rows based on a column value...。您没有在任何地方测试任何列值。
  • @EdMorton,我仍然对 OP 对“distinct”的使用感到困惑,但我会跟随你的专栏#1 = 1...
【解决方案2】:

我猜是因为您没有提供我们可以测试的示例输入和预期输出,但听起来您只需要:

shuf infile.csv | awk -F, '$1==1{ print > ("outfile" (NR%2)+1 ".csv") }'

如果这不是您想要的,请编辑您的问题以包含简洁、可测试的样本输入和预期输出。

【讨论】:

  • 嘿,埃德,我很抱歉 - 菜鸟的错误。我对上面的内容进行了一些编辑。不需要保留标题,我实际上在编辑中将其删除。我不确定您在代码中的哪个位置将返回的值分成两半并输出到 2 个不同的文件(训练和测试)?所以说 col[1]==1 中有 4 行。我需要 2 个附加到 train.csv 和 2 个附加到 test.csv。您是通过“(NR%2)+1”位执行此操作吗?我不熟悉 NR 参数。你能帮我看看吗?
  • 好的,我删除了保留标题的代码。我的答案中的那个脚本是否符合您的要求?
  • 它必须为 csv 更新:shuf test.csv | awk -F ',' '$1==1{ print >> ("outfile" (NR%2)+1 ".csv") }' // 现在它只是抓取列值 = 1。我如何得到它继续遍历所有不同的列值(有 138933)并将每个不同的值拆分+附加到 2 个输出文件?
  • 这是一个非常模糊和模棱两可的问题。只需创建一个带有简洁、可测试的示例输入和预期输出的问题,以演示您要解决的问题。如果不清楚,请参阅How to Ask,并特别注意有关提供minimal reproducible example 的部分。
  • 感谢 Ed 的留言。奇怪的人会简单地投票反对写最终答案...... 8(。回答你关于为什么“>>”的问题......我注意到使用“>”只捕获了 col[ 中不同值的第一次出现1]而不是找到所有相似的值然后将它们分成两半并附加到两个输出文件。正如所讨论的,我的原始CSV在col [1]中有超过2000万行重复的> 130k唯一值。将'>'更改为'> >' 成功了。