Performance

有沒有類似時間序列的莖葉圖?

  • January 27, 2020

當想要快速查看一系列值的分佈時,莖葉圖是一個非常簡單但功能強大的工具。教電腦畫它需要幾分鐘,或者你可以簡單地用手來做。

唯一的問題是它不保留值的順序,其中有時包含有用的資訊。我一直在嘗試想一種同樣簡單的方法來繪製具有保留順序的時間序列,但未能提出一些建議。

使用 X 軸上的時間和 Y 軸上的值來製作正常時間序列圖表的明顯解決方案存在這樣一個問題,即在進入實際渲染之前需要大量的準備工作。它在概念上遠沒有莖葉圖那麼簡單。

有什麼東西嗎?還是我要求的不可能?

哦,還有一個我差點忘記的重要要求:我希望它可以在行緩衝終端上輕鬆列印…


我在這裡問的原因是因為我的主要案例是健康指標和來自伺服器的其他樣本。在排除系統故障的原因時,最好快速了解某些子系統隨時間推移的行為方式。

我不確定這完全是我想要的,因為它有點複雜,但到目前為止它已經滿足了我的需求:

我開始使用溫度,這些溫度都在 25-60 °C 範圍內,所以我可以簡單地複制一串*以創建一種它們的條形圖:

$ cat temps2.txt | perl -pe 'print "*" x $_ . " ";'
******************************************** 44.0
*************************************************** 51.0
******************************************* 43.0
********************************************* 45.0
************************************** 38.0
**************************************** 40.0
*********************************** 35.0
************************************ 36.0
******************************** 32.0
******************************** 32.0
******************************* 31.0
******************************* 31.0
******************************** 32.0
******************************** 32.0
******************************* 31.0
************************************ 36.0
******************************** 32.0
************************************ 36.0
******************************* 31.0
*********************************** 35.0
************************************ 36.0
************************************ 36.0
********************************* 33.0
******************************* 31.0
******************************** 32.0
******************************* 31.0
********************************* 33.0
******************************** 32.0
******************************** 32.0
************************************ 36.0

當然,這僅適用於方便範圍內的值,但當它們存在時它非常有效——當它們不存在時,只需向$_表示重複次數的變數添加一些算術操作即可。

例如,每秒的平均處理器執行隊列長度(對我來說在 0-8 範圍內)可以乘以 10 以在輸出中顯示移位:

$ cat runq.txt | perl -pe 'print "*" x ($_ * 10) . " ";'
0
0
0
0
******************** 2
********** 1
******************** 2
**************************************** 4
****************************** 3
**************************************** 4
****************************** 3
****************************** 3
******************** 2
******************** 2
********** 1
********** 1
********** 1
************************************************************ 6
********** 1
********** 1
********** 1
0
0

這絕對可以滿足我的需求。


當然,作為我自己,我採取了這種方式並創建了一個大腳本,其中包括自動計算和更新座標變換,以及系統平均和自然過程限制的流式計算:

$ cat temps2.txt | ./limits.pl
----------------------------------------------------------------
X:    51.0           [            | *         ]
X:    43.0         [           * |            ]
X:    45.0            [         |          ]
X:    38.0          [      *   |          ]
X:    40.0           [      *  |        ]
X:    35.0           [   *    |        ]
X:    36.0           [    *  |        ]
X:    32.0           [ *     |       ]
X:    32.0           [ *    |      ]
X:    31.0           [*     |     ]
X:    31.0           [*    |     ]
X:    32.0           [ *   |     ]
X:    32.0           [ *   |    ]
X:    31.0           [*   |    ]
X:    36.0           [    *     ]
X:    32.0           [ *  |     ]
X:    36.0          [     |     ]
X:    31.0          [ *   |     ]
X:    35.0          [    *|     ]
X:    36.0          [     |    ]
X:    36.0          [     *    ]
X:    33.0          [   * |    ]
X:    31.0          [ *   |    ]
X:    32.0          [  * |     ]
X:    31.0          [ *  |    ]
X:    33.0          [   *|    ]
X:    32.0          [  * |    ]
X:    32.0          [  * |    ]
X:    36.0          [    |*   ]
UPL=42.1
Xbar=35.2
LPL=28.2

還附有此腳本的未清理原始碼。這是初稿,請原諒糟糕的程式碼。

#!/usr/bin/env perl

use v5.26;
use strict;
use warnings;
use List::Util qw( min max );

my $max_width = 52;

my $n = 0;
my $xbar = 0;
my $mrbar = 0;
my $lpl;
my $upl;

sub print_values {
   print "\n";
   printf "UPL=%.1f\n", $upl;
   printf "Xbar=%.1f\n", $xbar;
   printf "LPL=%.1f\n", $lpl;
}

$SIG{INT} = \&print_values;

my $min_y;
my $max_y;

my $xprev;
while (my $x = <>) {
   $n++;
   $xbar *= $n - 1;
   $xbar += $x;
   $xbar /= $n;

   if (defined($xprev)) {
       my $mr = abs ($x - $xprev);
       $mrbar *= $n - 2;
       $mrbar += $mr;
       $mrbar /= $n - 1;

       $lpl = $xbar - $mrbar * 2.66;
       $upl = $xbar + $mrbar * 2.66;

       my $space_changed;

       # If any point is about to be drawn outside of the screen space, expand
       # the space to include the currently drawn points and then some.
       if (min($lpl, $x) < $min_y or max($upl, $x) > $max_y) {
           my $min_diff = abs($min_y - min($lpl, $x));
           my $max_diff = abs($max_y - max($upl, $x));
           # Change min and max values in slightly larger steps to avoid
           # changing the space too often with a drifting process.
           $min_y -= $min_diff * 2;
           $max_y += $max_diff * 2;
           $space_changed = 1;
       }
       if ($min_y == $max_y) {
           $max_y = $min_y + 1;
       }

       my %screen_coords;
       $screen_coords{lpl} = $lpl;
       $screen_coords{upl} = $upl;
       $screen_coords{xbar} = $xbar;
       $screen_coords{x} = $x;

       # Transform the recorded values to the screen space.
       for my $coord (keys %screen_coords) {
           # Set offset to 0.
           $screen_coords{$coord} -= $min_y;
           # Divide by range to scale down to 0–1.
           $screen_coords{$coord} /= ($max_y - $min_y);
           # Scale up again to proper width.
           $screen_coords{$coord} *= ($max_width - 1);
       }

       # Render the recorded values into an array of characters.
       my @characters = split('', ' ' x $max_width);
       $characters[$screen_coords{xbar}] = '|';
       $characters[$screen_coords{lpl}] = '[';
       $characters[$screen_coords{upl}] = ']';
       $characters[$screen_coords{x}] = '*';

       # Print a separator whenever the space needs to be expanded.
       if ($space_changed) {
           printf ('-' x ($max_width + 12) . "\n");
       }

       printf "X: %7.1f %s\n", $x, join('', @characters);
   } else {
       $min_y = $x;
       $max_y = $x;
   }

   $xprev = $x;
}

print_values;

引用自:https://serverfault.com/questions/1000300