Wenn Sie schnell einen Blick auf die Verteilung einer Wertefolge werfen möchten, ist das Stamm-Blatt-Diagramm ein unglaublich einfaches und dennoch leistungsstarkes Tool. Es dauert nur wenige Minuten, dem Computer beizubringen, es zu zeichnen, oder Sie können es ganz einfach von Hand erledigen.
Das einzige Problem ist, dass die Reihenfolge der Werte, die manchmal nützliche Informationen enthalten, nicht beibehalten wird. Ich habe versucht, mir eine ebenso einfache Möglichkeit auszudenken, eine Zeitreihe mit beibehaltener Reihenfolge darzustellen, aber mir ist nichts eingefallen.
Die naheliegende Lösung, ein normales Zeitreihendiagramm mit Zeit auf der X-Achse und Werten auf der Y-Achse zu erstellen, leidet unter dem Problem, dass es ziemlich viel Vorarbeit erfordert, bevor man mit der eigentlichen Darstellung beginnen kann. Es ist konzeptionell bei weitem nicht so einfach wie das Stamm-Blatt-Diagramm.
Gibt es etwas, das möglich ist? Oder ist das, worum ich bitte, unmöglich?
Oh, und eine wichtige Anforderung, die ich fast vergessen hätte: Ich möchte, dass dies auf einem zeilengepufferten Terminal problemlos druckbar ist …
Der Grund, warum ich hier frage, ist, dass mein Hauptanwendungsfall hierfür Gesundheitsmetriken und andere Beispiele von Servern sind. Wenn man Ursachen für ein fehlerhaftes System ausschließt, wäre es schön, schnell ein Gespür dafür zu bekommen, wie sich ein bestimmtes Subsystem im Laufe der Zeit verhalten hat.
Antwort1
Ich bin nicht sicher, ob das genau das ist, was ich suche, da es ein bisschen komplex ist, aber es hat meine Anforderungen bisher erfüllt:
Ich begann mit Temperaturen zu arbeiten, die alle praktischerweise im Bereich von 25–60 °C lagen, sodass ich einfach eine Reihe von Werten replizieren konnte, *
um daraus eine Art Balkendiagramm zu erstellen:
$ 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
Dies funktioniert natürlich nur bei Werten, die in einem geeigneten Bereich liegen, ist aber unglaublich effektiv, wenn dies der Fall ist. Und wenn nicht, kann man der $_
Variablen, die die Anzahl der Wiederholungen angibt, einfach einige arithmetische Manipulationen hinzufügen.
Beispielsweise kann die durchschnittliche Länge der Prozessor-Run-Warteschlange pro Sekunde (die bei mir im Bereich 0–8 liegt) mit 10 multipliziert werden, um in der Ausgabe sichtbare Verschiebungen zu erhalten:
$ 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
Dies hätte meine Bedürfnisse absolut befriedigt.
Natürlich habe ich es übertrieben und ein großes Skript erstellt, das die automatische Berechnung und Aktualisierung der Koordinatentransformationen sowie die Streaming-Berechnung des Systemdurchschnitts und der natürlichen Prozessgrenzen umfasst:
$ 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
Unsauberer Quellcode zu diesem Skript ist ebenfalls beigefügt. Dies ist der erste Entwurf, also entschuldigen Sie bitte den schlechten Code.
#!/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;