修改CSV文件并保留订单

时间:2015-06-04 20:48:14

标签: perl csv

接下来的问题是我试图解决的更复杂问题的简化示例。我想保留代码的结构,特别是使用%hash来存储每个患者的结果,但我不需要将数据文件读入内存(但我找不到一种方法来阅读我的csv数据文件从最后逐行。)

我的样本数据由患者发生的事件组成。可以将患者添加到研究中(事件= B)或者他可以死亡(事件= D)或退出研究(事件= F)。死亡和退出是每个患者的唯一两种可能结果。

对于每个事件,我都有发生日期(从给定时间点开始的小时数),每个患者的唯一ID号,事件和结果(每个患者的字段设置为0)。

我正在尝试编写一个代码,通过在新患者的每次添加旁边添加来更改输入文件,他的最终结果是什么(死亡或退出。)

为了做到这一点,我从最后读取文件,每当我遇到病人的死亡或退出时,我都会填写一个与患者ID匹配的哈希值。当我遇到一个事件告诉我已经将新患者添加到研究中时,我将他的ID与哈希中的ID匹配,并将“结果”的值从0更改为D或F.

我已经能够编写一个从底部读取文件的代码,然后使用更新的Outcome值创建一个新的修改文件。问题是,因为我从下到上读取输入文件并在读取后打印每一行,输出文件的顺序相反,我不知道如何更改它。另外,理想情况下我不想创建新文件,我只想修改输入文件。但是,我在每次尝试时都失败了。

示例数据:

Data,PatientNumber,Event,Outcome
25201027,562962838335407,B,0
25201028,562962838335408,B,0
25201100,562962838335407,D,0
25201128,562962838335408,F,0

我的代码:

#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;

open (my $fh_input, "<", "mini_test2.csv")
  or die "cannot open > mini_test2.csv: $!";
my @lines = <$fh_input>;
close $fh_input;

open (my $fh_output, ">>", "Revised_mini_test2.csv")
  or die "cannot open > Revised_mini_test2.csv: $!";

my $length = scalar(@lines);
my %outcome;

my @input_variables;

for (my $i = 1; $i < @lines; $i++){
    chomp($lines[$length-$i]);
    @input_variables=split(/,/, $lines[$length - $i]);

    if ($input_variables[2] eq "D" || $input_variables[2] eq "F"){
        $outcome{$input_variables[1]} = $input_variables[2];
        my $line = join(",", @input_variables);
        print $fh_output $line . "\n";
    }
    elsif($input_variables[2] eq "B") {
         $input_variables[3]=$outcome{$input_variables[1]};
         my $line = join(",", @input_variables);
         print $fh_output $line . "\n";
    }
    else{
        # necessary since the actual data has many more possible "Events"
        my $line = join(",", @input_variables);
        print $fh_output $line . "\n";
    }
}
close $fh_output;

编辑:所需的输出应为

Data,PatientNumber,Event,Outcome
25201027,562962838335407,B,D
25201028,562962838335408,B,F
25201100,562962838335407,D,0
25201128,562962838335408,F,0

此外,另一个复杂因素是患者出院后的唯一患者ID被重新使用。这意味着我无法进行第一次传递并为每位患者存储结果,而第二次传递则更新结果值。

编辑2:让我澄清一下,当我说每个患者都有一个“唯一ID”时,我的意思是在研究中不能同时有两个具有相同身份证的患者。但是,如果患者退出研究,他的身份证会重新使用。

2 个答案:

答案 0 :(得分:0)

更新

我刚刚阅读了您的其他信息,一旦他们退出研究,就会重复使用患者编号。为什么要设计一个我不知道的系统,但它就是

在不将文件读入数组的情况下编写简单的内容变得更加困难,这就是我在这里所做的事情

use strict;
use warnings;
use 5.010;
use autodie;

open my $fh, '<', 'mini_test2.csv';
my @data;
while ( <$fh> ) {
    chomp;
    push @data, [ split /,/ ];
}

my %outcome;

for ( my $i = $#data; $i > 0; --$i ) {
    my ($patient_number, $event) = @{$data[$i]}[1,2];
    if ( $event =~ /[DF]/ ) {
        $outcome{$patient_number} = $event;
    }
    elsif ( $event =~ /[B]/ ) {
        $data[$i][3] = delete $outcome{$patient_number} // 0;
    }
}

print join(',', @$_), "\n" for @data;

<强>输出

Data,PatientNumber,Event,Outcome
25201027,562962838335407,B,D
25201028,562962838335408,B,F
25201100,562962838335407,D,0
25201128,562962838335408,F,0

有几种方法可以解决这个问题。我选择在文件中进行两次传递,首先在哈希中累积每个患者的结果,然后替换B记录中的所有结果字段

use strict;
use warnings;
use 5.010;
use autodie;

use Fcntl ':seek';

my %outcome;

open my $fh, '<', 'mini_test2.csv';
<$fh>; # Drop header

while ( <$fh> ) {
  chomp;
  my @fields = split /,/;
  my ($patient_number, $event) = @fields[1,2];
  if ( $event =~ /[DF]/ ) {
    $outcome{$patient_number} = $event;
  }
}

seek $fh, 0, SEEK_SET; # Rewind

print scalar <$fh>; # Copy header

while ( <$fh> ) {
  chomp;
  my @fields = split /,/;
  my ($patient_number, $event) = @fields[1,2];
  if ( $event !~ /[DF]/ ) {
    $fields[3] = $outcome{$patient_number} // 0;
  }
  print join(',', @fields), "\n";
}

<强>输出

Data,PatientNumber,Event,Outcome
25201027,562962838335407,B,D
25201028,562962838335408,B,F
25201100,562962838335407,D,0
25201128,562962838335408,F,0

答案 1 :(得分:-1)

我们可以做的不是在每个阶段打印出行,而是将其写回行数组。然后我们可以在最后打印出来。

for (my $i=$#lines; i>=0; i--)
{
    chomp $lines[$i];
    @input_variables = split /,/, $lines[$i];
    if ($input_variables[2] eq "D" || $input_variables[2] eq "F")
    {
         $outcome{$input_variables[1]}=$input_variables[2];
    }else
    {
         $input_variables[3]=$outcome{$input_variables[1]};
    }
    $line[$i] = join ",", @input_variables;
}

$, = "\n";     #Make list seperator for printing a newline.

print $fh_output @lines;

关于修改原始文件的第二个问题。可以使用模式“+&lt;”,“+&gt;”或“+&gt;&gt;”打开用于读取和写入的文件。 不要这样做!由于必须逐字符替换数据,因此容易出错。

“修改”现有文件的标准方法是重命名,从重命名的文件中读取,使用原始名称写入新文件,然后删除临时文件。

my $file_name = "mini_test2.csv";
my $tmp_file_name = $file_name . ".tmp";
rename $file_name, $tmp_file_name;
open (my $fh_input, "<", $tmp_file_name)
    or die "cannot open > $tmp_file_name: $!";

open (my $fh_output, ">>", $file_name)
    or die "cannot open > $file_name: $!";

#Your code to process the data.

close $fh_input;
close $fh_output;

#delete the temp file
unlink $tmp_file_name;

但是,在您的情况下,您立即将所有数据放入内存中。只是打开写那些clobbers现有文件

open (my $fh_output, ">", "mini_test2.csv")
    or die "cannot open > mini_test2.csv: $!";