Need help collapsing a list and obtaining totals in perl - arrays

Hi I have a large list of data:
http://paste-it.net/public/y17027d/
It is 67859 rows by 10 columns. The 6th column contains values that represent Z-Scores from 1 to 6 in .01 increments. What I would like to do is to total all of the other column values that have the same Z-score value, but my current code is not working.
What I have now prints out values but the totals for each Z-Score are incorrect.
Here is my code:
#! /usr/bin/perl
use strict;
use warnings;
use POSIX;
use Data::Dumper;
my $input = $ARGV[0];
open (DATAFILE, $input) or die $!;
open(OUT,">>"."final.output.txt");
my($line,$fMeasure,$filename,$recall,$precision,$z_score,$computer_calls,$johns_calls,$false_negatives,$false_positives,$true_positives,$count);
$fMeasure=$filename=$recall=$precision=$z_score=$computer_calls=$johns_calls=$false_negatives=$false_positives=$true_positives=$count = 0;
my %stats=();
my %zscore=();
while($line = <DATAFILE>){
# Chop off new line character, skip the comments and empty lines.
chomp($line);
my #temp = split(/\t/, $line);
$true_positives = $temp[0];
$false_positives = $temp[1];
$false_negatives = $temp[2];
$johns_calls = $temp[3];
$computer_calls = $temp[4];
$z_score = $temp[5];
$fMeasure = $temp[6];
$precision = $temp[7];
$recall = $temp[8];
$filename = $temp[9];
$stats{$z_score}{$filename}[0] = $true_positives;
$stats{$z_score}{$filename}[1] = $false_positives;
$stats{$z_score}{$filename}[2] = $johns_calls;
$stats{$z_score}{$filename}[3] = $computer_calls;
$stats{$z_score}{$filename}[4] = $fMeasure;
$stats{$z_score}{$filename}[5] = $precision;
$stats{$z_score}{$filename}[6] = $recall;
$stats{$z_score}{$filename}[6] = $filename;
$zscore{$z_score}++;
}
my $false_positives_new = 0;
my $true_positives_new = 0;
my $johns_calls_new = 0;
my $computer_calls_new = 0;
my $file_name = 0;
foreach $z_score ( sort keys %stats ) {
foreach $filename( keys %{$stats{$z_score}} ){
my $tp = $stats{$z_score}{$filename}[0];
my $fp = $stats{$z_score}{$filename}[1];
my $jc = $stats{$z_score}{$filename}[2];
my $cc = $stats{$z_score}{$filename}[3];
my $fn = $stats{$z_score}{$filename}[6];
#print "$z_score\t$jc\n";
$false_positives_new = $false_positives + $fp;
$true_positives_new = $true_positives + $tp;
$johns_calls_new = $johns_calls + $jc;
$computer_calls_new = $computer_calls + $cc;
#print OUT "$fn\n";
}
print OUT"$true_positives_new\t$false_positives_new\t$johns_calls_new\t$computer_calls_new\t$z_score \n";
$false_positives_new = 0;
$true_positives_new = 0;
$johns_calls_new = 0;
$computer_calls_new = 0;
$file_name = 0;
}
close(OUT);
close (DATAFILE);
I know that I must be doing something wrong but I am not able to figure out what. Any help would be greatly appreciated. Thank you

OK. I was able to get the data from pastebin and I think the following code does what you want.
#! /usr/bin/perl
use strict; use warnings;
use Data::Dumper;
my ($input) = #ARGV;
open my $DATAFILE, '<', $input
or die "Cannot open '$input': $!";
my #field_names = qw(
fMeasure
recall
precision
z_score
computer_calls
johns_calls
false_negatives
false_positives
true_positives
count
);
my #track_fields = qw(
false_positives
false_negatives
johns_calls
computer_calls
);
my (%stats, %by_zscore);
while ( my $line = <$DATAFILE> ) {
last unless $line =~ /\S/;
chomp $line;
my #temp = split /\t/, $line;
my $filename = pop #temp;
my %fields;
#fields{ #field_names } = #temp;
my $z_score = $fields{z_score};
$stats{ $z_score }{$filename} = \#temp;
for my $f ( #track_fields ) {
$by_zscore{$z_score}{ $f } += $fields{ $f };
}
}
print Dumper \%by_zscore;

I think you want to say
$false_positives_new = $false_positives_new + $fp;
# etc.
instead of
$false_positives_new = $false_positives + $fp;

Related

Perl: Inserting values into specific columns of CSV file

I have CSV data of the form:
S.No,Label,Customer1,Customer2,Customer3...
1,label1,Y,N,Y
2,label2,N,Y,N
...
I need to reproduce the "label" to the left of "customer" columns marked with Y - and have nothing ("") to the left of columns marked with N.
Expected output:
S.No,Label,Customer1,Customer1,Customer2,Customer2,Customer3,Customer3...
1,label1,label1,Y,"",N,label1,Y
2,label2,"",N,label2,Y,"",N
When opened using Excel, it would look like this:
S.No Label Customer1 Customer1 Customer2 Customer2 Customer3 Customer3...
1 label1 label1 Y N label1 Y
2 label2 N label2 Y N
The two leftmost columns, referring to S.No and the original "Label" column, are constant.
What is the simplest way to do this? I tried the following code:
use strict;
use warnings;
my $nonIncludesFile = "nonIncludes.csv";
open(my $xfh, "+>", $nonIncludesFile) or warn "Unable to open $nonIncludesFile, $!";
chomp( my $header = <$xfh> );
my #names = split ",", $header;
my #names1;
my #fields;
my #fields1;
for(my $j=0; $j< scalar(#names); $j++)
{
$names1[$j] = $names[$j];
}
while(<$xfh>)
{
my $nonIncLine = $_;
$nonIncLine = chomp($nonIncLine);
#fields = split ",", $nonIncLine;
next if $. == 1; #skip the first line
for(my $i = 0; $i < scalar(#fields) -2; $i++) #Number of "customers" = scalar(#fields) -2
{
$fields1[0] = $fields[0];
$fields1[1] = $fields[1];
if('Y' eq $fields[ $i + 2 ])
{
$fields1[$i+2] = 'Y';
substr(#fields1, $i + 1, 0, $fields[1]); #insert the label to the left - HERE
}
else
{
$fields1[$i+2] = 'N';
substr(#fields1, $i + 1, 0, "");
}
}
}
print $xfh #names1;
print $xfh #fields1;
close($xfh);
This however complains of "substr outside of string" at the line marked by "HERE".
What am I doing wrong? And is there any simpler (and better) way to do this?
Something like this maybe?
#!/usr/bin/perl
use strict;
use warnings;
#read the header row
chomp( my ( $sn, $label, #customers ) = split( /,/, <DATA> ) );
#double the 'customers' column headings (one is suffixed "_label")
print join( ",", $sn, $label, map { $_ . "_label", $_ } #customers ), "\n";
#iterate data
while (<DATA>) {
#strip trailing linefeed
chomp;
#extract fields with split - note breaks if you've quoted commas inline.
my ( $sn, $label, #row ) = split /,/;
print "$sn,$label,";
#iterate Y/N values, and either prints "Y" + label, or anything else + blank.
foreach my $value (#row) {
print join( ",", $value eq "Y" ? $label : "", $value ),",";
}
print "\n";
}
__DATA__
S.No,Label,Customer1,Customer2,Customer3
1,label1,Y,N,Y
2,label2,N,Y,N
Assumes you don't have any fruity special characters (e.g. commas) in the fields, because it'll break if you do, and you might want to consider Text::CSV instead.
It is always much better to post some usable test data than write a something like this question
However, it looks like your data has no quoted fields or escaped characters, so it looks like you can just use split and join to process the CSV data
Here's a sample Perl program that fulfils your requirement. The example output uses your data as it is. Each line of data has to be processed backwards so that the insertions don't affect the indices of elements that are yet to be processed
use strict;
use warnings 'all';
use feature 'say';
while ( <DATA> ) {
chomp;
my #fields = split /,/;
for ( my $i = $#fields; $i > 1; --$i ) {
my $newval =
$. == 1 ? $fields[$i] :
lc $fields[$i] eq 'y' ? $fields[1] :
'';
splice #fields, $i, 0, $newval;
}
say join ',', #fields;
}
__DATA__
S.No,Label,Customer1,Customer2,Customer3...
1,label1,Y,N,Y
2,label2,N,Y,N
output
S.No,Label,Customer1,Customer1,Customer2,Customer2,Customer3...,Customer3...
1,label1,label1,Y,,N,label1,Y
2,label2,,N,label2,Y,,N

How can I find the common items from two arrays fast?

I am trying to findy the common lines among two tab separated files based one field.
One line of the first file:
1 52854 s64199.1 A . . . PR GT 0/0
One line of the second file:
chr1 52854 . C T 215.302 . AB=0.692308;ABP=7.18621;AC=1;AF=0.5;AN=2;AO=9;CIGAR=1X;DP=13;DPB=13;DPRA=0;EPP=3.25157;EPPR=3.0103;GTI=0;LEN=1;MEANALT=1;MQM=60;MQMR=60;NS=1;NUMALT=1;ODDS=17.5429;PAIRED=0;PAIREDR=0.25;PAO=0;PQA=0;PQR=0;PRO=0;QA=318;QR=138;RO=4;RPP=3.25157;RPPR=5.18177;RUN=1;SAF=0;SAP=22.5536;SAR=9;SRF=1;SRP=5.18177;SRR=3;TYPE=snp;technology.illumina=1;BVAR GT:DP:RO:QR:AO:QA:GL 0/1:13:4:138:9:318:-5,0,-5
Based on the second field (52854) in this example I have a much.
Here is my code which finds the common ones, but my files are quite large and takes a lot of time.
Is there any way to speed up the process?
Thank you very much in advance.
#!/app/languages/perl/5.14.2/bin/perl
use strict;
use warnings;
my $map_file = $ARGV[0];
my $vcf_file = $ARGV[1];
open my $map_info, $map_file or die "Could not open $map_file: $!";
my #map_array = ();
my #vcf_array = ();
while( my $mline = <$map_info>) {
chomp $mline;
my #data1 = split('\t', $mline);
my $pos1 = $data1[1];
push (#map_array, $pos1);
}
open my $vcf_info, $vcf_file or die "Could not open $vcf_file: $!";
while( my $line = <$vcf_info>) {
if ($line !~ m/^#/) {
push (#vcf_array, $line);
}
}
foreach my $a (#map_array) {
chomp $a;
foreach my $b (#vcf_array) {
chomp $b;
my #data = split('\t', $b);
my $pos2 = $data[1];
my $ref2 = $data[3];
my $allele = $data[4];
my $genotype = $data[9];
if ($a == $pos2) {
print $pos2 . "\t" . $ref2. "\t".$allele."\t".$genotype. "\n";
#print "$b\n";
}
}
}
Here's a version that should run much faster than your own
It reads the map file and stores each pos field in hash %wanted. Then it reads through the second file and checks whether the record is in the list of wanted values. If so then it splits the record and prints the fields you require
Note that I haven't been able to test this beyond making sure that it compiles
use strict;
use warnings;
use 5.010;
use autodie;
my ( $map_file, $vcf_file ) = #ARGV;
my %wanted;
{
open my $map_fh, '<', $map_file;
while ( <$map_fh> ) {
chomp;
my $pos = ( split /\t/, $_, 3 )[1];
++$wanted{$pos};
}
}
{
open my $vcf_fh, '<', $vcf_file;
while ( <$vcf_fh> ) {
next if /^#/;
chomp;
my $pos = ( split /\t/, $_, 3 )[1];
next unless $wanted{$pos};
my ( $ref, $allele, $genotype ) = ( split /\t/ )[3, 4, 9];
print join("\t", $pos, $ref, $allele, $genotype), "\n";
}
}
Below please find minimal modification of your script for hash based searches
use strict;
use warnings;
my $map_file = $ARGV[0];
my $vcf_file = $ARGV[1];
my %vcf_hash;
open( my $vcf_info, $vcf_file) or die "Could not open $vcf_file: $!";
while( my $line = <$vcf_info>) {
next if $line =~ m/^#/; # Skip comment lines
chomp $line;
my (#data) = split(/\t/, $line);
die unless #data >= 10; # Check number of fields in the input line
my ($pos) = $data[1];
# $. - line number in the file
$vcf_hash{$pos}{$.} = \#data;
}
open( my $map_info, $map_file) or die "Could not open $map_file: $!";
while( my $mline = <$map_info>) {
chomp $mline;
my (#data) = split(/\t/, $mline);
die unless #data >= 2; # Check number of fields in the input line
my ($pos) = $data[1];
if( exists $vcf_hash{$pos}) {
my $hash_ref = $vcf_hash{$pos};
for my $n (sort{$a<=>$b} keys %$hash_ref) {
my $array_ref = $hash_ref->{$n};
my $pos2 = $array_ref->[1];
my $ref2 = $array_ref->[3];
my $allele = $array_ref->[4];
my $genotype = $array_ref->[9];
print $pos2 . "\t" . $ref2. "\t".$allele."\t".$genotype. "\n";
}
}
}
The script may be improved further to reduce memory use if you use huge data files.
There is not a need to keep your map_file in memory but just keys. It is good to make them keys in a hash which you use for existence checking. You don't have to keep your vcf_file in memory as well, but you can just make a decision to output or not.
#!/app/languages/perl/5.14.2/bin/perl
use strict;
use warnings;
use autodie;
use constant KEY => 1;
use constant FIELDS => ( 1, 3, 4, 9 );
my ( $map_file, $vcf_file ) = #ARGV;
my %map;
{
my $fh;
open $fh, '<', $map_file;
while (<$fh>) {
$map{ ( split /\t/, $_, KEY + 2 )[KEY] } = undef;
}
}
{
my $fh;
open $fh, '<', $vcf_file;
while (<$fh>) {
next if /^#/;
chomp;
my #data = split /\t/;
print join "\t", #data[FIELDS] if exists $map{ $data[KEY] };
}
}

Perl retrieve index of array on regex match and print

I am looking to extract columns based off of header names in a comma (or tab) delimited file. I have a scalar variable that matches many header possibilities I named '$Acct_Name', among ones. I want to read the file(column headers), match it to what I have in '$Acct_Name' and print the matched column along with its data etc.
Here is my code:
open(FILE, "list_2.txt") or die "Cannot open file: $!";
my $Account_Name = qr/^Acct ID$|^Account No$|^Account$|^ACCOUNT NUMBER$|Account Number|Account.*?Number|^Account$|^Account #$|^Account_ID$|^Account ID$/i;
my $CLIENT = qr/^CLIENT_NAME$|^Account Long Name$|^ACCOUNT NAME$|^Account Name$|^Name$|portfolio.*?description|^Account Description$/i;
while (my $line = <FILE>) {
chomp $line;
my #array = split(/,/, $line);
my %index;
#index{#array} = (0..$#array);
my $Account_Name_ = $index{$Account_Name};
if (my ($matched) = grep $array[$_] =~ /$Account_Name/, 0..$#array) {
$Account_Name_ = $matched;
my $CLIENT_ = $index{$CLIENT};
if (my ($matched) = grep $array[$_] =~ /$CLIENT/, 0..$#array) {
$CLIENT_ = $matched;
print $array[$Account_Name_],",",$array[$CLIENT_],"\n";
}
}
}
close(FILE);
Data, list_2.txt
Account number,order_num,Name
dj870-1234,12334566,josh trust 1992
My Results
Account number,Name
Desried Out
Account number,Name
dj870-1234,josh
For some reason I am only able to print the column names based on the match. How can I grab the data as well?
You need to move your print statement to output your data lines - these do not match, so in the original code the print statement is never reached !
use warnings;
open(FILE, "list_2.txt") or die "Cannot open file: $!";
my $Account_Name = qr/^Acct ID$|^Account No$|^Account$|^ACCOUNT NUMBER$|Account Number|Account.*?Number|^Account$|^Account #$|^Account_ID$|^Account ID$/i;
my $CLIENT = qr/^CLIENT_NAME$|^Account Long Name$|^ACCOUNT NAME$|^Account Name$|^Name$|portfolio.*?description|^Account Description$/i;
my ($Account_Name_, $CLIENT_);
while (my $line = <FILE>) {
chomp $line;
my #array = split(/,/, $line);
if (my ($matched) = grep $array[$_] =~ /$Account_Name/, 0..$#array) {
$Account_Name_ = $matched;
if (my ($matched) = grep $array[$_] =~ /$CLIENT/, 0..$#array) {
$CLIENT_ = $matched;
}
}
print $array[$Account_Name_],",",$array[$CLIENT_],"\n";
}
close(FILE);

Use of uninitalized value in concatenation <.> or string at xyz.pl line 27

I am writing this perl program and want to create an array which should be stored in output file according to the values of for loop. I am new to this programming.
This is my code
use strict;
use warnings;
open( my $out_fh, ">", "output.txt" ) || die("Cannot open file.\n");
my ( $x, $y, $i, $j, $k, $p, $q );
my ( #Xrow, #b, #b_l, #w );
print("Enter the number of rows:\n");
$p = <STDIN>;
chop($p);
print("Enter the number of columns:\n");
$q = <STDIN>;
chop($q);
$x = 2**$p;
$y = 2**$q;
#Xrow = ( #b, #b_l, #w );
for ( $i = 0; $i < $x * $y; $i = $i + 1 ) {
for ( $j = 0; $j < $x; $j = $j + 1 ) {
for ( $k = 0; $k < $y; $k = $k + 1 ) {
$Xrow[$i] = "$b[$j],$b_l[$j],$w[$k]";
foreach (#Xrow) {
print $out_fh "$_\n";
}
}
}
}
So output should look like for example p=q=1
Xrow0 b0 b_l0 w0
Xrow1 b1 b_l1 w0
Xrow2 b0 b_l0 w1
Xrow3 b1 b_l1 w1
so it should print like this in output file without any braces and "="
But I am getting error like this
Use of uninitialized value in concatenation<.> or string at xyz.pl in line 27
Use of uninitialized value within #b in concatenation<.> or string at xyz.pl in line 27
You don't populate the arrays anywhere, so they stay empty.
But, in fact, you need no arrays to get the output you want.
Additional advice:
Prefer chomp to chop.
Declare the variables when you need them, not at the top of the program/subroutine.
How I'd do it:
#!/usr/bin/perl
use warnings;
use strict;
open my $OUT, '>', 'output.txt' or die "Cannot open file.\n";
print "Enter the number of rows:\n";
my $p = <STDIN>;
chomp $p;
print "Enter the number of columns:\n";
my $q = <STDIN>;
chomp $q;
my $x = 2 ** $p;
my $y = 2 ** $q;
my $i = 0;
for (my $j = 0; $j < $x; $j = $j + 1) {
for (my $k = 0; $k < $y; $k = $k + 1) {
print {$OUT} "Xrow$i b$k b_l$k w$j\n";
++$i;
}
}

Read CSV file and save in 2 d array

I am trying to read a huge CSV file in 2 D array, there must be a better way to split the line and save it in the 2 D array in one step :s
Cheers
my $j = 0;
while (<IN>)
{
chomp ;
my #cols=();
#cols = split(/,/);
shift(#cols) ; #to remove the first number which is a line header
for(my $i=0; $i<11; $i++)
{
$array[$i][$j] = $cols[$i];
}
$j++;
}
CSV is not trivial. Don't parse it yourself. Use a module like Text::CSV, which will do it correctly and fast.
use strict;
use warnings;
use Text::CSV;
my #data; # 2D array for CSV data
my $file = 'something.csv';
my $csv = Text::CSV->new;
open my $fh, '<', $file or die "Could not open $file: $!";
while( my $row = $csv->getline( $fh ) ) {
shift #$row; # throw away first value
push #data, $row;
}
That will get all your rows nicely in #data, without worrying about parsing CSV yourself.
If you ever find yourself reaching for the C-style for loop, then there's a good chance that your program design can be improved.
while (<IN>) {
chomp;
my #cols = split(/,/);
shift(#cols); #to remove the first number which is a line header
push #array, \#cols;
}
This assumes that you have a CSV file that can be processed with a simple split (i.e. the records contain no embedded commas).
Aside: You can simplify your code with:
my #cols = split /,/;
Your assignment to $array[$col][$row] uses an unusual subscript order; it complicates life.
With your column/row assignment order in the array, I don't think there's a simpler way to do it.
Alternative:
If you were to reverse the order of the subscripts in the array ($array[$row][$col]), you could think about using:
use strict;
use warnings;
my #array;
for (my $j = 0; <>; $j++) # For testing I used <> instead of <IN>
{
chomp;
$array[$j] = [ split /,/ ];
shift #{$array[$j]}; # Remove the line label
}
for (my $i = 0; $i < scalar(#array); $i++)
{
for (my $j = 0; $j < scalar(#{$array[$i]}); $j++)
{
print "array[$i,$j] = $array[$i][$j]\n";
}
}
Sample Data
label1,1,2,3
label2,3,2,1
label3,2,3,1
Sample Output
array[0,0] = 1
array[0,1] = 2
array[0,2] = 3
array[1,0] = 3
array[1,1] = 2
array[1,2] = 1
array[2,0] = 2
array[2,1] = 3
array[2,2] = 1

Resources