# Chart ~~glätten~~ stauchen, um Datenmenge zu reduzieren, und um Übersichtlichkeit beizubehalten

• Hab es noch einmal überarbeitet. Jetzt sieht es schon "geglätteter" aus und mehr so in die Richtung, wie ich dachte, dass es aussehen soll:

https://i.postimg.cc/VkMBnhyj/grafik.png

Reduzierung von 89 auf 26 Datenpunkte.

``````import jetbrains.exodus.entitystore.Entity;
import jetbrains.exodus.entitystore.EntityIterable;
import jetbrains.exodus.entitystore.PersistentEntityStore;
import jetbrains.exodus.entitystore.PersistentEntityStores;
import org.jetbrains.annotations.NotNull;

import java.util.*;

public class StatisticCollectionShrink {
private static class ShrinkBlock {
private final long t1;
private final long t2;
private final ArrayList<Entity> entities = new ArrayList<>();

public ShrinkBlock(final long t1) {
this.t1 = t1;
this.t2 = t1 + 1000 * 60 * 60 * 3;
}

private boolean contains(final Entity e) {
return contains((long) Objects.requireNonNull(e.getProperty("time")));
}

private boolean contains(final long t) {
return t >= t1 && t <= t2;
}

private double getAverage1() {
int n1 = entities.size();
double sum1 = 0;
for (final Entity entity : entities) {
sum1 += (double) Objects.requireNonNull(entity.getProperty("sum"));
}
return sum1 / n1;
}

private double[] getAverage2() {
int n1 = entities.size();
double[] sum2 = new double[n2];
for (final Entity entity : entities) {
int i = 0;
for (final Entity asset : entity.getLinks("asset")) {
sum2[i++] += Double.parseDouble((String) Objects.requireNonNull(asset.getProperty("sum")));
}
}
for (int i = 0; i < n2; i++) {
sum2[i] /= n1;
}
return sum2;
}

private long getMidTime() {
return t1 + (t2 - t1) / 2;
}

private @NotNull EntityIterable getFirstAssets() {
}
}

public StatisticCollectionShrink() {
try (PersistentEntityStore store = PersistentEntityStores.newInstance("rowsData")) {
store.executeInTransaction(txn -> {
ArrayList<Entity> oldEntities = new ArrayList<>();
EntityIterable measurements = txn.sort("Measurement", "time", true);
for (final Entity measurement : measurements) {
}
System.out.println("size() = " + measurements.size());

ArrayList<ShrinkBlock> blocks = new ArrayList<>();
long endTime = (Long) Objects.requireNonNull(oldEntities.get(oldEntities.size() - 1).getProperty("time"));
while (blocks.get(blocks.size() - 1).t2 < endTime) {
}
for (final Entity entity : oldEntities) {
for (final ShrinkBlock block : blocks) {
if (block.contains(entity)) {
break;
}
}
}
for (final ShrinkBlock block : blocks) {
if (block.entities.isEmpty()) {
continue;
}
double sum1 = block.getAverage1();
double[] sum2 = block.getAverage2();
long midTime = block.getMidTime();
Entity newEntity = txn.newEntity("Measurement");
newEntity.setProperty("time", midTime);
newEntity.setProperty("sum", sum1);
int i = 0;
for (final Entity oldAsset : block.getFirstAssets()) {
Entity newAsset = txn.newEntity("Asset");
newAsset.setProperty("name", Objects.requireNonNull(oldAsset.getProperty("name")));
newAsset.setProperty("amount", Objects.requireNonNull(oldAsset.getProperty("amount")));
newAsset.setProperty("sum", String.valueOf(sum2[i++]));
}
}

for (final Entity oldEntity : oldEntities) {
oldEntity.delete();
}
System.out.println("size() = " + txn.sort("Measurement", "time", true).size());
});
}
}
}

``````

• @Fragender
Mal eine Frage. Deine Datenreihen scheinen ja fast symmetrisch zu sein. Und ferner erscheinen mir die Werte ein wenig zu diskret. Wenn ich z.B. die obere violette Kurve anschaue, so scheinen sich nachfolgende Werte entweder nur minimal zu ändern oder immer in 170 +- Epsilon Schritte. Und ferner scheint diese symmetrisch zu der gelben Datenreihe zu sein.

Wäre es da nicht mal besser einfach eine Aufzeichnung mit größerer Periode zu machen?

• Wir haben ein ähnliches Problem und gehen diesen Weg (Sonderfälle mal außer Acht gelassen):

Wir extrahieren aus den Ausgangsdaten eine Anzahl von Blöcken und aus jedem Block wird das Minimum und Maximum benutzt. Damit verlieren wir die Extrema nicht. Funktioniert natürlich nur, wenn die Ausgangsdatenmenge mind. doppelt so groß ist wie die Zieldatenmenge.

• Mal eine Frage. Deine Datenreihen scheinen ja fast symmetrisch zu sein. Und ferner erscheinen mir die Werte ein wenig zu diskret. Wenn ich z.B. die obere violette Kurve anschaue, so scheinen sich nachfolgende Werte entweder nur minimal zu ändern oder immer in 170 +- Epsilon Schritte. Und ferner scheint diese symmetrisch zu der gelben Datenreihe zu sein.

Das Verhalten ist in etwa fast symmetrisch, aber es ist nicht exakt symmetrisch! Was ist zu sehen?: Das sind USD-Beträge von Assets (auf der y-Achse). Dabei wird an der Börse immer ein Asset gegen ein anderes getauscht. Geringe Fees fallen dabei an.

Folgendes ist dabei ideal: Häufige, kleine Anpassungen der Summen und Summen nahe der Mitte. Sprich, wenn es "out-of-bounds" läuft (eine Asset-Summe ist nahe 0 und die andere nahe Max), wäre das suboptimal. Am besten wäre, wenn sich die Asset-Pairs häufig kreuzen würden. So viel zu der konkreten Problemdomäne.

eine Anzahl von Blöcken und aus jedem Block wird das Minimum und Maximum benutzt. Damit verlieren wir die Extrema nicht.

Das klingt wirklich sinnvoller, als immer den Mittelwert aus 3 oder mehr Summen zu bilden... Aber rechne ich dann (Min + Max) / 2.0?

• Das klingt wirklich sinnvoller, als immer den Mittelwert aus 3 oder mehr Summen zu bilden... Aber rechne ich dann (Min + Max) / 2.0?

Ne, aus jedem Block fallen 2 Datenpunkte raus, das Minimum und das Maximum. Und die werden dann im Diagramm angezeigt. Angenommen, du hast 2000 Datenpunkte und möchtest du 500 anzeigen. Dann bildest du 250 Blöcke á 8 Datenpunkte und bestimmst aus jedem Block das Minimum und Maximum. Daraus bildest du dann den Graphen mit 500 Datenpunkten.

• Das klingt wirklich sinnvoller, als immer den Mittelwert aus 3 oder mehr Summen zu bilden... Aber rechne ich dann (Min + Max) / 2.0?

Ne, aus jedem Block fallen 2 Datenpunkte raus, das Minimum und das Maximum. Und die werden dann im Diagramm angezeigt. Angenommen, du hast 2000 Datenpunkte und möchtest du 500 anzeigen. Dann bildest du 250 Blöcke á 8 Datenpunkte und bestimmst aus jedem Block das Minimum und Maximum. Daraus bildest du dann den Graphen mit 500 Datenpunkten.

Coole Methode. Kannte ich noch gar nicht. Danke dafür

• Aha, und die anderen fliegen raus, das klingt gut. Werde mich gleich mal an die Umsetzung wagen.

• @DocShoe Ich komme damit nicht weiter. Ich/wir haben etwas übersehen. Ich habe ja nicht nur eine Chartlinie, sondern eine Linienschar von 12 Linien, wenn man so will. Ich kann zwar jeweils die Mini- und Maxima bestimmen, aber wie wähle ich dann zwei Datenpunkte aus, (~) zu denen alle Chartlinien dann quasi auch ein Mini- bzw. Maxima bilden? Hoffentlich kann man mich einigermaßen verstehen ... (ich bin kein Mathe-Fuchs)

Versuch bisher:

``````        private Entity[] getSignificantAssetEntities() {
if (entities.isEmpty())
throw new NullPointerException();
if (entities.size() == 1)
return new Entity[]{entities.get(0)};
Entity[] maxYs = new Entity[n2];
Entity[] minYs = new Entity[n2];
for (final Entity entity : entities) {
int i = 0;
for (final Entity asset : entity.getLinks("asset")) {
double sum = Double.parseDouble((String) Objects.requireNonNull(asset.getProperty("sum")));
double maxSum = maxYs[i] == null ? Double.MIN_VALUE : Double.parseDouble((String) Objects.requireNonNull(maxYs[i].getProperty("sum")));
double minSum = minYs[i] == null ? Double.MAX_VALUE : Double.parseDouble((String) Objects.requireNonNull(minYs[i].getProperty("sum")));
if (sum > maxSum) {
maxYs[i] = asset;
}
if (sum < minSum) {
minYs[i] = asset;
}
i++;
}
}

// return ???
}
``````

• Glaube, hab 'ne Idee ...

Ansatz 1: Den RMS bzw. RMSD berechnen: https://www.statisticshowto.com/quadratic-mean/ und https://en.wikipedia.org/wiki/Root-mean-square_deviation_of_atomic_positions

oder
Ansatz 2: Jeweils den einfachen Mittelwert sowie Minima und Maxima ermitteln, dann schauen, ob der Mittelwert näher am Minima oder Maxima liegt, und dementsprechend ersteres oder zweiteres verwenden; wodurch die Extrema auch beibehalten werden sollten. (Kenne den Namen dafür net)

Das Ganze sollte auch stabil sein ... das heißt, die Mehrfachhintereinanderausführung des Verfahrens sollte nach dem zweiten Mal das Ergebnis nicht mehr ändern.

Was haltet ihr davon?

• So, habe es, Dank @DocShoe , jetzt endlich hinbekommen.

Reduzierung von 116 auf 52 Datenpunkte und das Verfahren ist stabil, das Ergebnis verändert sich nicht durch mehrmalige Aufrufe.

Vorher (oben) und nachher (unten): https://i.postimg.cc/K8F7HNgj/grafik.png

Danke, dass ihr mir geholfen habt. Anbei noch der Code.

``````import jetbrains.exodus.entitystore.Entity;
import jetbrains.exodus.entitystore.EntityIterable;
import jetbrains.exodus.entitystore.PersistentEntityStore;
import jetbrains.exodus.entitystore.PersistentEntityStores;

import java.util.ArrayList;
import java.util.Objects;

public class StatisticCollectionShrink {
private static class ShrinkBlock {
private static final long t0 = 1000 * 60 * 60 * 4;
private final long t1;
private final long t2;
private final long t3;
private final ArrayList<Entity> entities = new ArrayList<>();

public ShrinkBlock(final long t1) {
this.t1 = t1;
this.t2 = t1 + t0;
this.t3 = t1 + t0 / 2;
}

public ShrinkBlock getNext() {
return new ShrinkBlock(t2);
}

private boolean contains(final Entity e) {
return contains((long) Objects.requireNonNull(e.getProperty("time")));
}

private boolean contains(final long t) {
return t >= t1 && t < t2;
}

private Entity[] getSignificantAssetEntities() {
if (entities.size() < 2)
return null;
Entity[] maxYs = new Entity[n];
Entity[] minYs = new Entity[n];
for (final Entity entity : entities) {
int i = 0;
for (final Entity asset : entity.getLinks("asset")) {
if (maxYs[i] == null) {
maxYs[i] = asset;
}
if (minYs[i] == null) {
minYs[i] = asset;
}
double sum = Double.parseDouble((String) Objects.requireNonNull(asset.getProperty("sum")));
double maxSum = Double.parseDouble((String) Objects.requireNonNull(maxYs[i].getProperty("sum")));
double minSum = Double.parseDouble((String) Objects.requireNonNull(minYs[i].getProperty("sum")));
if (sum > maxSum) {
maxYs[i] = asset;
}
if (sum < minSum) {
minYs[i] = asset;
}
i++;
}
}
Entity maxY = maxYs[0];
Entity minY = minYs[0];
for (int i = 0; i < n; i++) {
double sum1 = Double.parseDouble((String) Objects.requireNonNull(maxY.getProperty("sum")));
double sum2 = Double.parseDouble((String) Objects.requireNonNull(maxYs[i].getProperty("sum")));
double sum3 = Double.parseDouble((String) Objects.requireNonNull(minY.getProperty("sum")));
double sum4 = Double.parseDouble((String) Objects.requireNonNull(minYs[i].getProperty("sum")));
if (sum1 < sum2) {
maxY = maxYs[i];
}
if (sum3 > sum4) {
minY = minYs[i];
}
}
return new Entity[]{maxY, minY};
}
}

public StatisticCollectionShrink() {
try (PersistentEntityStore store = PersistentEntityStores.newInstance("rowsData")) {
store.executeInTransaction(txn -> {
// collect old entities
ArrayList<Entity> oldEntities = new ArrayList<>();
EntityIterable measurements = txn.sort("Measurement", "time", true);
for (final Entity measurement : measurements) {
}
System.out.println("size() = " + measurements.size());

// build blocks
ArrayList<ShrinkBlock> blocks = new ArrayList<>();
long endTime = (long) Objects.requireNonNull(oldEntities.get(oldEntities.size() - 1).getProperty("time"));
while (blocks.get(blocks.size() - 1).t2 <= endTime) {
}
for (final Entity entity : oldEntities) {
for (final ShrinkBlock block : blocks) {
if (block.contains(entity)) {
break;
}
}
}

// insert new entities
for (final ShrinkBlock block : blocks) {
if (block.entities.size() < 2) {
continue;
}
Entity[] oldAssets = Objects.requireNonNull(block.getSignificantAssetEntities());
long t1 = block.t1;
long t3 = block.t3;
long d1 = (long) Objects.requireNonNull(oldMaxEntity.getProperty("time")) - t1;
long d2 = (long) Objects.requireNonNull(oldMinEntity.getProperty("time")) - t1;
if (d1 <= d2) {
{
Entity newEntity = txn.newEntity("Measurement");
newEntity.setProperty("time", t1);
newEntity.setProperty("sum", Objects.requireNonNull(oldMaxEntity.getProperty("sum")));
for (final Entity oldAsset : oldMaxEntity.getLinks("asset")) {
Entity newAsset = txn.newEntity("Asset");
newAsset.setProperty("name", Objects.requireNonNull(oldAsset.getProperty("name")));
newAsset.setProperty("amount", Objects.requireNonNull(oldAsset.getProperty("amount")));
newAsset.setProperty("sum", Objects.requireNonNull(oldAsset.getProperty("sum")));
}
}
{
Entity newEntity = txn.newEntity("Measurement");
newEntity.setProperty("time", t3);
newEntity.setProperty("sum", Objects.requireNonNull(oldMinEntity.getProperty("sum")));
for (final Entity oldAsset : oldMinEntity.getLinks("asset")) {
Entity newAsset = txn.newEntity("Asset");
newAsset.setProperty("name", Objects.requireNonNull(oldAsset.getProperty("name")));
newAsset.setProperty("amount", Objects.requireNonNull(oldAsset.getProperty("amount")));
newAsset.setProperty("sum", Objects.requireNonNull(oldAsset.getProperty("sum")));
}
}
} else {
{
Entity newEntity = txn.newEntity("Measurement");
newEntity.setProperty("time", t1);
newEntity.setProperty("sum", Objects.requireNonNull(oldMinEntity.getProperty("sum")));
for (final Entity oldAsset : oldMinEntity.getLinks("asset")) {
Entity newAsset = txn.newEntity("Asset");
newAsset.setProperty("name", Objects.requireNonNull(oldAsset.getProperty("name")));
newAsset.setProperty("amount", Objects.requireNonNull(oldAsset.getProperty("amount")));
newAsset.setProperty("sum", Objects.requireNonNull(oldAsset.getProperty("sum")));
}
}
{
Entity newEntity = txn.newEntity("Measurement");
newEntity.setProperty("time", t3);
newEntity.setProperty("sum", Objects.requireNonNull(oldMaxEntity.getProperty("sum")));
for (final Entity oldAsset : oldMaxEntity.getLinks("asset")) {
Entity newAsset = txn.newEntity("Asset");
newAsset.setProperty("name", Objects.requireNonNull(oldAsset.getProperty("name")));
newAsset.setProperty("amount", Objects.requireNonNull(oldAsset.getProperty("amount")));
newAsset.setProperty("sum", Objects.requireNonNull(oldAsset.getProperty("sum")));
}
}
}
}

// delete old entities
for (final Entity oldEntity : oldEntities) {