時系列の幹葉図のようなものはありますか?

時系列の幹葉図のようなものはありますか?

一連の値の分布をすばやく確認したい場合、幹葉図は信じられないほどシンプルですが強力なツールです。コンピューターに図を描くように教えるのに数分かかりますが、手作業で簡単に描くこともできます。

唯一の問題は、値の順序が保持されないことです。値の順序には、役に立つ情報が含まれている場合があります。順序を保持した時系列をプロットする同様に簡単な方法を考えようとしましたが、何も思いつきません。

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

もちろん、これは便利な範囲内の値でのみ機能しますが、便利な範囲内の値の場合、非常に効果的です。そうでない場合は、$_繰り返し回数を示す変数に算術操作を追加するだけで済みます。

たとえば、1 秒あたりのプロセッサ実行キューの平均長 (私の場合は 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;

関連情報