【问题标题】:Aligning data table created from perl hash对齐从 perl 哈希创建的数据表
【发布时间】:2011-11-30 04:13:29
【问题描述】:

我正在尝试编写一个脚本来处理来自行为测试设备的输出。我需要在生成的 CSV 文件中按时间戳对齐所有数据。这是一个问题:测试运行之间的开始时间不同(它很接近,但不准确 - 可能会相差几秒钟到几分钟)。我可以得到我想要的输出,我想我对如何对齐所有变量有一个好主意,但不知道如何实现它。

所有数据都在具有两个级别 (%hash{id}{vars}) 的哈希中,所有变量都存储为数字以保持简单(变量名称在打印输出时从数组中读取)。从输入文件中抓取所有数据后,脚本将遍历哈希并打印出如下数据:

Variable 1
ID #1   data1   data2   data3...
ID #2   data1   data2   data3...
...
Variable 2
...

等等。

这些是 24 小时的录音。所有受试者的最后一个数据点 (var=20) 都很轻:数据在白天和晚上读取“ON”或“OFF”。我能看到的最佳对齐方法是使用关灯标记来对齐数据。

我的想法如下:
1. 查找每个 ID 的第一个位置,其中 var '20' = 'OFF' 并记录位置
2.找出哪个ID的OFF位置最大(即最早开始记录的那个)
3. 将空值对添加到每个其他主题,直到所有主题的 OFF 位置相同。

例如,如果每分钟记录一次数据,并且一个受试者的 OFF 时间比所有其他受试者晚 5 分钟,则将 5 个空数据点添加到所有其他受试者以对齐数据。

这必须针对每个对象的所有数据点进行,而不仅仅是灯的开/关测量。

这种方法行得通吗?如果是这样,我该如何实现?

**请注意,我需要能够将其打包为独立脚本以在多台计算机上运行,​​因此我不能指望默认未安装的 perl 模块。

--按请求编辑:示例。输入数据看起来像这样(它是一个 CSV 文件)

ID,     TIME,      DATA1,  DATA2,  DATA3, [...] ,  LIGHT  
Subj1,  10:00:00,  data1,  data2,  data3, [...] ,  ON  
Subj1,  10:00:30,  data1,  data2,  data3, [...] ,  ON  
Subj1,  10:01:00,  data1,  data2,  data3, [...] ,  OFF  
Subj1,  10:01:00,  data1,  data2,  data3, [...] ,  OFF  

对于另一个主题,数据可能如下所示:

ID,     TIME,      DATA1,  DATA2,  DATA3, [...] ,  LIGHT  
Subj2,  09:59:27,  data1,  data2,  data3, [...] ,  ON  
Subj2,  09:59:57,  data1,  data2,  data3, [...] ,  ON  
Subj2,  10:00:27,  data1,  data2,  data3, [...] ,  ON  
Subj2,  10:00:57,  data1,  data2,  data3, [...] ,  OFF  
Subj2,  10:01:27,  data1,  data2,  data3, [...] ,  OFF  

脚本从所有文件中取出每一行,并将它们添加到一个以 ID 为键的散列中,每个数据列都有一个级别,以列号为键。对于这两个文件,哈希看起来像这样:

$VAR1 = {
         'Subj1' => {
                     '1' => [
                             data1
                             data1
                             ...
                             ]
                      '2' => [
                             data2
                             data2
                             ...
                             ]
                     ...
                     '20' => [
                             ON
                             ON
                             ...
                    }
         'Subj1' => {
                     '1' => [
                             data1
                             data1
                             ...
                             ]
                      '2' => [
                             data2
                             data2
                             ...
                             ]
                     ...
                     '20' => [
                             ON
                             ON
                             ...
                    }
        };

使用 foreach 循环输出数据:

foreach my $k (sort {$a cmp $b} keys %data) { 
    print OUT $k, "\,";
    foreach my $d ( @{ $data{$k}{$i} } ) { print OUT $d, "\,"; }
    print OUT "\n";
    }

输出如下所示:

TIME  
Subj1,  10:00:00,  10:00:30,  10:01:00,  10:01:30,
Subj2,  09:59:27,  09:59:57,  10:00:27,  10:00:57,  10:01:27,
DATA1
Subj1,  data1,  data1,  data1,  data1,  data1,  
Subj2,  data2,  data2,  data2,  data2,  data2,  data2,
[ ... all other data ... ]
LIGHT
Subj1,  ON,  ON,  OFF, OFF,
Subj2,  ON,  ON,  ON,  OFF, OFF,

我需要做的是按 LIGHT 中的 ON/OFF 列对齐所有数据,方法是添加如下所示的空值:

TIME  
Subj1,          ,  10:00:00,  10:00:30,  10:01:00,  10:01:30,
Subj2,  09:59:27,  09:59:57,  10:00:27,  10:00:57,  10:01:27,
DATA1
Subj1,       ,  data1,  data1,  data1,  data1,  data1,  
Subj2,  data2,  data2,  data2,  data2,  data2,  data2,
[ ... all other data ... ]
LIGHT
Subj1,    ,  ON,  ON,  OFF, OFF,
Subj2,  ON,  ON,  ON,  OFF, OFF,

试图弄清楚如何最好地做到这一点。对不起,这很长......

【问题讨论】:

  • 您应该提供输入示例和相应的输出,以便我们更好地了解您想要什么。最好包括一些“问题案例”,以了解需要克服的障碍。你的描述让我有点困惑,就目前而言。
  • 我在 24 小时内以 30 秒的增量收集了 20 次不同的测量值 - 这些文件非常大,当所有受试者的数据组合在一起时会变得更大。我不确定我能得到它有多简单。我将编辑问题以查看是否有帮助。
  • 哦。您想传播您的讲座以使其符合时间线吗?

标签: perl hashtable alignment


【解决方案1】:

知道了! “最大”是那里的关键。 jwd,不知道为什么,但我无法调整您的脚本以处理数据(不断收到“不能将字符串用作严格的哈希引用”错误)。但它给了我所需的灵感。

在处理文件时,我实现了一个计数器,该计数器会递增,直到达到第一个 OFF。这与主题 ID 作为键值对 ($offset{$id} = $count) 一起传递给哈希。

处理完文件后,我从哈希中提取最大值,然后使用从最大值中减去的原始键值重新定义值。

输出时,我使用您的代码作为基础添加额外的逗号来填充数据。最终的相关部分如下所示:

my $max_off = max values %offset;

foreach my $k ( keys %offset ) {
    $offset{$k} = $max_off - $offset{$k};
    }

foreach my $k (sort {$a cmp $b} keys %data) { 
    print OUT $k, "\,";
    print OUT ',' x ($offset{$k});
    foreach my $d ( @{ $data{$k}{$i} } ) { print OUT $d, "\,"; }
        print OUT "\n";
        }
    }

正是我需要的。感谢您的建议!

【讨论】:

    【解决方案2】:

    这适合你吗?

    它确实使用了List::Util::max,但这已经是标准的一段时间了,如果你没有它也很容易自己编写。

    use List::Util qw(max);
    use strict;
    use warnings;
    
    my $ALLDATA = {
             'Subj1' => {
                         '1' => [
                                 'data1',
                                 'data1',
                                 ],
                          '2' => [
                                 'data2',
                                 'data2',
                                 ],
                         '20' => [
                                 'ON',
                                 'ON',
                                 'OFF',
                                 ]
                        },
             'Subj2' => {
                         '1' => [
                                 'data1',
                                 'data1',
                                 ],
                          '2' => [
                                 'data2',
                                 'data2',
                                 ],
                         '20' => [
                                 'ON',
                                 'ON',
                                 'ON',
                                 'OFF',
                                 'OFF',
                                 ]
                        },
            };
    
    sub num_ons_before_first_off
    {
        my $n = 0;
        foreach(@_)
        {
            last if $_ eq 'OFF';
            $n++;
        }
        return $n;
    }
    
    # store a 'numons' piece of data for each subject, for use later
    foreach my $subject(values(%$ALLDATA))
    {
        $subject->{'numons'} = num_ons_before_first_off(@{$subject->{'20'}}); 
    }
    
    # calculate the max 'numons' across all subjects
    my $max_ons = max(map { $_->{'numons'} } values(%$ALLDATA));
    
    foreach my $k(keys(%$ALLDATA))
    {
        my $subject = $ALLDATA->{$k};
    
        #output leading blank entries
        print ',' x ($max_ons - $subject->{'numons'});
    
        #output the real data
        foreach my $data(@{$subject->{'20'}})
        {
            print "$data,";
        }
        print "\n";
    }
    

    希望如何将其扩展到数据输出的其余部分是显而易见的。

    您是否需要问题中的间距? 你说它是 CSV,所以我猜不是。如果这很重要,我可以更新。

    【讨论】:

    • 不,间距只是为了便于查看示例中的数据结构,CSV 文件中没有实际的空格。明天我会给你的代码试一试,看看我是否能让它按预期工作。感谢您迄今为止的帮助!
    【解决方案3】:

    这不是答案,但不适合评论:

    当您说要“按 LIGHT 中的 ON/OFF 列对齐所有数据”时,您的意思是右对齐所有内容吗?

    例如,如果您有以下数据:

    Subj1,ON,ON,OFF,
    Subj2,ON,ON,ON,OFF,OFF,
    

    输出会是这样吗?

    Subj1,   ,   , ON,  ON, OFF,
    Subj2, ON, ON, ON, OFF, OFF,
    

    或者你希望它是这样的:

    Subj1,   , ON, ON, OFF,    ,       <-- Note trailing blank entry
    Subj2, ON, ON, ON, OFF, OFF,
    

    也就是说,对齐第一个“OFF”,如您的文字描述中那样?

    【讨论】:

    • 第二个输出 - 因为并非所有录制都在同一时间点停止。所有都应该至少 24 小时,但有些可能运行时间更长,所以有些行可能比其他行更长。我可以使用对原始脚本的添加来获得每个主题的偏移计数(计数器在每一行递增,直到达到一个 OFF 值)。如果我可以使用它在输出 foreach 循环期间添加空点,它将起作用。一直在尝试看看如何做到这一点。
    猜你喜欢
    • 2011-05-08
    • 2011-04-20
    • 2013-12-20
    • 2014-02-27
    • 2018-08-26
    • 1970-01-01
    • 1970-01-01
    • 2018-12-07
    • 2019-06-01
    相关资源
    最近更新 更多