當想要快速查看一系列值的分佈時,莖葉圖是一個非常簡單但功能強大的工具。教電腦繪製它需要幾分鐘,或者您可以輕鬆地手動完成。
唯一的問題是它不保留值的順序,而值的順序有時包含有用的信息。我一直在嘗試想出一種同樣簡單的方法來繪製保留順序的時間序列,但未能想出一些辦法。
製作常規時間序列圖(X 軸上為時間,Y 軸上為值)的明顯解決方案存在一個問題,即在進入實際渲染之前需要進行大量準備。它在概念上遠不像莖葉圖那麼簡單。
有什麼東西嗎?或者我所要求的是不可能的?
哦,還有一個我差點忘記的重要要求:我希望它可以在行緩衝終端上輕鬆列印...
我在這裡問的原因是因為我的主要用例是健康指標和來自伺服器的其他樣本。當排除系統故障的原因時,最好能快速直觀地了解某些子系統隨時間的變化。
答案1
我不確定這完全是我想要的,因為它有點複雜,但到目前為止它已經滿足了我的需求:
我開始使用溫度,這些溫度都在 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;