Konferenz-Herbst 2017

(English version)

Der Herbst dieses Jahres hält viele spannende Konferenzen bereit.

Gerade ist die Industrial Data Science 2017 in Dortmund zu Ende gegangen. Einen Tag lang hielten verschiedene Vertreter der Industrie hauptsächlich aus Deutschland interessante Vorträge über ihre Data-Science-Projekte. Es ging um komplexe Sachen wie die Vorhersage der Qualität von Stahl schon während des Verarbeitungsprozesses, von Engpässen in flexiblen Produktionsprozessen und um Produkt-Design mit automatischer Erstellung von Prozess- und Teilelisten. Die klassische Industrieanwendung „predictive maintenance“ haben diese führenden Unternehmen also bereits hinter sich. Allerdings wurden viele Projekte als noch nicht in Produktion befindlich angegeben – insofern liegt Österreich vielleicht noch gar nicht so weit zurück.

Das nächste Ereignis ist die Predictive-Analytics-Konferenz am 10. und 11. Oktober in Wien. Diese findet heuer schon zum 13. Mal statt, und ist jedes Jahr ein Pflichttermin für mich. Die Bandbreite der Vorträge in den letzten Jahren war sehr groß, und ich fand immer interessante Anregungen für meine Arbeit.

Weiter geht es dann zwischen 24. und 27. Oktober mit der Europäischen PostgreSQL-Konferenz, diesmal in Warschau. Hier werde ich über die erfolgreiche Migration der Mainframe-DB2-Datenbank nach PostgreSQL in München sprechen, und die Erfahrungen daraus.

Vom 10. bis 12. November findet dann das Pentaho-Community-Treffen in Mainz statt. Hier habe ich auch einen Vortrag eingereicht: Pentaho im Startup. Gemeint ist natürlich SCO2T. Auch wenn wir SCO2T mittlerweile nicht mehr als Startup, sondern als etabliertes Unternehmen und Marktführer bezeichnen, sind die Erfahrungen, die ich unter anderem mit Pentaho gemacht habe, hoffentlich fürs Publikum interessant. Da es schon zu viele technische Präsentationen gab, wurde ich gebeten, im Business-Track zu präsentieren, es werden also eher die Anwendungsfälle als die technischen Details zur Sprache kommen.

2017 Fall Conferences

In this autumn, many interesting conferences present themselves to the data scientist.

The Industrial Data Science 2017 conference in Dortmund, Germany is already over. For an entire day, people from leading (mostly German) industry companies gave interesting talks about their data science projects. The topics were complex: predicting steel quality during the milling process, bottlenecks in flexible production processes and product design with automatic creation of process and parts lists. These companies are already well behind the basic industrial use case of predictive maintenance. However, many of the projects are still not in production – so companies in other countries didn‘t yet lost the race.

The next conference is Predictive Analytics in Vienna, with mostly German talks. This is the 13th iteration of the conference, and I always consider it a mandatory event for me. In the last years, the range of the topics was enormous, and I was always finding new approaches and ideas for my own work.

Still in October, the European PostgreSQL Conference will take place in Warsaw this time. There I‘ll present the successfull migration of a mainframe DB2 database to Postgres in Munich and the experiences I gained from this project.

In November, this year‘s Pentaho Community Meeting is in Mainz. I also submitted a talk there about Pentaho in a startup, with the startup being SCO2T, of course. We don‘t call SCO2T a startup anymore, but an established service provider and market leader. Still, the audience is hopefully interested in learning about the use cases with Pentaho in this environment. There were already too many technical presentations submitted, so I‘ll present in the business track. This means that the talk will be more about use cases than technical details.

PostgreSQL-JDBC-Optionen für RapidMiner und andere Software

(English version)

In verschiedenen Projekten habe ich mich genauer mit den Optionen des PostgreSQL-JDBC-Treibers beschäftigen müssen und damit konkrete Probleme von Java-Anwendungen, unter anderem RapidMiner gelöst. Ich möchte meine Erfahrungen hier teilen und einige der JDBC-Parameter genauer beschreiben.

defaultRowFetchSize (default: 0)

Dieser Parameter steuert die Anzahl der auf einmal angeforderten Zeilen in einem ResultSet. Der Vorgabewert von 0 ergibt eine gute Performance, solange das Abfrageergebnis nicht zu groß wird. Wenn aber sehr viele Daten zurückgegeben werden, die das Programm dann nicht mal braucht, kann der Speicher des Java-Programms (oder RapidMiner Studio) vollständig verbraucht werden.

Meine Empfehlung: zur Sicherheit eine hohe, aber noch sinnvoll in den Speicher passende Zeilenanzahl wählen, z. B. 10.000 oder 100.000. Dadurch wird nicht alles gleich aus der Datenbank geladen, sondern erst wenn die Anwendung die nachfolgende Zeile anfordert.

RapidMiner lädt die Abfrageergebnisse komplett in den Speicher, diese Einstellung bewirkt also nicht so viel wie bei einem Programm, das die Daten sequenziell liest und verarbeitet. Sie kann aber beim Einschränken des Speicherverbrauchs eines Prozesses helfen, und auch bei Verwendung von Stream Database.

stringtype (default: VARCHAR)

Prepared Statements sind bei der Entwicklung von Datenbankanwendungen sehr empfehlenswert. Auch RapidMiner verwendet sie z. B. in Write Database, Update Database und optional in Read Database.

Diese Statements haben folgende Form:


SELECT a, b, c FROM tabelle WHERE c1 = ? and c2 = ?;

INSERT INTO tabelle (a, b, c) VALUES (?, ?, ?);

Für jedes Fragezeichen wird ein Wert mit Datentyp eingegeben. Das vermeidet Typkonversionsfehler und SQL Injections, die sonst viel zu leicht auftreten (das ‚O’Connor‘-Problem).

stringtype = VARCHAR (die Standardeinstellung) bewirkt, daß PostgreSQL als String übergebene Parameter explizit als VARCHAR betrachtet. Das verursacht in einigen Situationen Probleme:


SELECT ... WHERE datumsfeld = ?; -- Datum als String übergeben

INSERT INTO booltabelle (boolfeld) VALUES (?); -- 'true' oder 'false' als String übergeben

Meine Empfehlung: stringtype = unspecified. Das erlaubt z. B. in RapidMiner die Verwendung von korrekt formatierten Datumsangaben in Nominal-Feldern, oder auch von Booleans (true/false) mit Write Database. Es hilft auch bei Legacy-Anwendungen, die mit einer anderen Datenbank entwickelt wurden, und z. B. Zeitstempel als Text an die Datenbank übergeben.

Es wären Situationen denkbar, in denen die Einstellung „unspecified“ Probleme verursachen kann, z. B. wenn die Eingabe nicht auf korrekt formatierte Daten eingeschränkt ist. Dann würde nicht schon das erste Einfügen fehlschlagen, sondern erst der Datensatz mit den falschen Daten. Die Ergebnisse in der Datenbank wären also unvollständig.

ApplicationName

Dieser Parameter kann mit einem beliebigen String belegt werden. Für Datenbank-Admins hilft es manchmal zu wissen, welche Anwendung und welcher User z. B. eine aufwändige Abfrage ausführt.

Meine Empfehlung: Den Namen der eigenen Anwendung eintragen.

currentSchema

Mit diesem Parameter kann der Suchpfad (search_path) festgelegt werden. PostgreSQL sucht standardmäßig in den Schemata pg_catalog, $user und public. Wenn die eigenen Tabellen z. B. im Schema „datamining“ liegen, können sie mit currentSchema=datamining direkt angesprochen werden.

Diese Einstellung kann in speziellen Situationen helfen, wenn z. B. eine migrierte Anwendung ein eigenes Schema verwendet und nicht darauf vorbereitet ist, Schema-Namen anzugeben.

Meine Empfehlung: Nur in begründeten Sonderfällen ändern. Die Änderung von search_path bewirkt, daß die JDBC-Verbindung sich anders verhält als z. B. ein SQL-Client (solange man diesen nicht auch umstellt). Das kann zu subtilen, erst später bemerkten Fehlern führen.

Angabe der JDBC-Parameter

Im Allgemeinen werden die Parameter an die URL der JDBC-Verbindung angehängt, z. B. so:

jdbc:postgresql://server/database?stringtype=unspecified&defaultRowFetchSize=10000&ApplicationName=user@RapidMiner

RapidMiner bietet in Manage Database Connections unter Advanced eine Liste der einstellbaren Parameter, die aber defaultRowFetchSize nicht anbietet. Solche Parameter kann man einfach an den Inhalt des Feldes „Database scheme“ anhängen und die URL überprüfen.

PostgreSQL JDBC parameters for RapidMiner and other software

Some of my projects required working with parameters of the PostgreSQL JDBC driver and using them to solve problems in Java applications like RapidMiner. I’d like to share what I learned and describe some of the parameters in detail.

defaultRowFetchSize (default: 0)

This parameter sets the number of rows that are fetched together in a ResultSet. The default value of 0 results in a good performance as long as the query result doesn’t become too large. But if the query returns a huge amount of data, the memory usage of the Java application (e. g. RapidMiner Studio) grows by a large amount.

I recommend setting a large number that limits the memory requirements, for example 10,000 or 100,000 according to the capabilities of the server and the application. This limits the number of rows fetched from the database initially. The subsequent rows will be fetched when requested by the application.

RapidMiner loads query results into the memory anyway, so this setting doesn’t change its behaviour much. Still, it can help limiting the memory usage of a process, and when using Stream Database.

stringtype (default: VARCHAR)

The recommended way to write SQL statements in application development is to use prepared statements. RapidMiner uses these in Write Database, Update Database and optionally in Read Database.

Prepared statements look like this:


SELECT a, b, c FROM tablename WHERE c1 = ? and c2 = ?;

INSERT INTO tablename (a, b, c) VALUES (?, ?, ?);

Each question mark gets a typed value assigned. This avoids type conversion errors and SQL injections that are all too easy to make otherwise (the ‚O’Connor‘ problem).

The default setting stringtype=VARCHAR forces PostgreSQL to always interpret string parameters with the VARCHAR type. This causes problems in some situations:


SELECT ... WHERE datecolumn = ?; -- date specified as string

INSERT INTO booltable (boolcolumn) VALUES (?); -- 'true' or 'false' strings

I recommend setting stringtype=unspecified. This adds flexibility e. g. in RapidMiner, for example nominal fields with dates or booleans (true/false) with the Write Database operator. It also helps with legacy applications developed with a different database in mind, that use strings to represent timestamps in the database.

There might be situations where „unspecified“ is not the right setting. If the input is not always correct, the process would only fail when encountering the wrong data, so it might end up with an incomplete data set in the database.

ApplicationName

This parameter takes an arbitrary string. It is helpful for database admins so they can see the application and the user behind long-running queries.

I recommend setting the name of the application in this parameter.

currentSchema

This parameter changes the search_path of the database. PostgreSQL searches for non-schema qualified objects in the following schemas: pg_catalog, $user, public. If your tables are in the schema „datamining“, you can set currentSchema=datamining so you can refer to them directly.

This setting can be useful in migration settings, e. g. when the legacy application uses tables in a schema but isn’t prepared to specify the schema name in queries.

I recommend leaving this setting empty and only changing it when required. When you change search_path, your JDBC connection in the application will work differently from e. g. the JDBC client (if that is left with the default settings). This might cause subtle problems later.

Setting JDBC parameters

Usually you can simply append the parameters to the JDBC connection URL like this:

jdbc:postgresql://server/database?stringtype=unspecified&defaultRowFetchSize=10000&ApplicationName=user@RapidMiner

RapidMiner offers a list of parameters in Manage Database Connections with the Advanced button. This list doesn’t offer defaultRowFetchSize, however. You can still specify this parameter by appending it to the „Database scheme“ input field and checking the URL below.

Linuxwochen-Vortrag: Open Source im Startup

Ich halte auch heuer einen Vortrag bei den Linuxwochen Wien, diesmal mit dem Titel „Open Source im Startup”. Es ist eine große Ehre, gleich den ersten Vortrag nach der Eröffnung halten zu dürfen.

Diesmal geht es weniger um mein Kernthema Data Science, sondern um allgemeine IT und den Einsatz verschiedener Open-Source-Lösungen in einem Startup. Ich bin ja „nebenbei“ der CIO (und gleichzeitig die IT-Abteilung) von SCO2T, dem Roller-Sharing in Wien.

Im Vortrag geht es um Technologien, die ich teilweise auch schon hier im Blog vorgestellt habe: PostgreSQL und PostGIS, Foreign Data Wrappers, den Pentaho-Stack, aber auch um neue Themen wie Web-APIs mit PostgREST, Traccar und so weiter.  Ich zeige einige Beispiele, wie man als Nicht-Programmierer mit eher dem Bereich Data Science zugehörigen Werkzeugen auch komplexe-IT-Systeme aufbauen kann.

Der Vortrag beginnt am Donnerstag, 4. 5. 2017 um 10 Uhr im Raum F0.01 am FH Technikum Wien, Höchstädtplatz 6.

Auf der Vortrags-Seite im Programm werden auch meine Folien hinterlegt werden, außerdem gibt es dort wie jedes Jahr die Möglichkeit, eine Bewertung des Vortrags abzugeben.

Es gibt wie jedes Jahr spannende Vorträge an allen drei Tagen, ich werde wohl öfters in einem der Hörsäle anzutreffen sein.

Window Functions in RapidMiner

(English version)

Window-Funktionen in RapidMiner

Richtige Datenbankserver wie PostgreSQL haben seit geraumer Zeit den SQL-99-Standard umgesetzt, der unter anderem Window Functions beschreibt.

Mit Window Functions lassen sich gruppenbezogene Statistiken und Kennzahlen berechnen, ohne die Ergebnismenge zu gruppieren. (Dies ist der Unterschied zur klassischen Aggregierung mit GROUP BY.) Es gibt eine Menge Anwendungsbeispiele: es lassen sich laufende Summen, gleitende Mittelwerte, Anteile innerhalb der Gruppe, gruppenweise Minima oder Maxima und noch viele andere Dinge berechnen. Manche Leute meinen, daß mit Window Functions eine neue Ära für SQL begonnen hat, und ich neige dazu, ihnen zuzustimmen.

RapidMiner hat bislang keine Window Functions eingebaut. Erfahrene Data Scientists, die Bedarf an dieser Funktionalität hatten, haben diese mit verschiedenen Operatoren (Loops, gruppenweise Aggregation und Join usw.) selbst nachbauen können, aber das ist alles andere als trivial.

Um die Funktionalität für einen größeren Benutzerkreis zu öffnen und gleichzeitig eine wiederverwendbare Implementierung zu schaffen, habe ich das Projekt rapidminer-windowfunctions ins Leben gerufen und eine erste funktionierende Implementierung hochgeladen.

Alle im RapidMiner-Operator „Aggregate“ eingebauten Aggregationsfunktionen lassen sich verwenden: sum, average, minimum, maximum usw. Zusätzlich sind die Funktionen row_number, rank und dense_rank verfügbar.

Im Projektordner sind der eigentliche Prozess und aktuell drei Beispielprozesse enthalten, um gleich verschiedene Anwendungsmöglichkeiten testen zu können.

Golf-Datensatz: Gruppenmittelwert der Temperatur und Rang innerhalb der Gruppe für Luftfeuchtigkeit
  • Golf: Der berühmte Golf-Dataset wird aus dem mitgelieferten Beispiel-Repository geladen. Für jede Gruppe basierend auf dem „Outlook“-Attribut wird die Durchschnittstemperatur berechnet und als Attribut hinzugefügt. Ein zweites neues Attribut enthält den Rang des Datensatzes bei Sortierung nach dem Wert der Luftfeuchtigkeit. Gleiche Werte erhalten den gleichen Rang.
  • Iris: Ein weiterer Standard-Dataset, Iris, wird geladen, und es wird pro Spezies der Durchschnittswert fürs Attribut „a3“ berechnet. Dieser Mittelwert wird dann genutzt, um Exemplare herauszufiltern, die mehr als 10 % vom Gruppenmittelwert entfernt sind.
  • Deals: Der mitgelieferte Dataset „Deals“ wird geladen. In jeder Kundengruppe aus Geschlecht und Zahlungsmethode werden die drei ältesten Kunden gesucht, die anderen werden herausgefiltert.

Das zeigt schon die Bandbreite der Möglichkeiten, die Window Functions bieten. Die Möglichkeit, sie leicht einzusetzen und Indikatoren sowie Kennzahlen wie „Transaktionszähler des Kunden im Monat“, „Anteil des Produkts an der Monatssumme“ und ähnliche erzeugen zu können, wird viele Prozesse vereinfachen, oder neue Möglichkeiten eröffnen.

Implementierung

Nach einigen Prüfungen, z. B. ob die benötigten Makros gesetzt sind, werden neue Attribute zur Gruppierung und Identifizierung von Examples angelegt. Das zusammengesetzte Gruppierungsattribut wird dabei aus den Werten der als groupFields angegebenen Attribute generiert. Das Identifizerungs-Attribut muß mit einem Groovy-Skript generiert werden, da Generate Id mit vielen Datensätzen nicht funktionieren würde. (Generate Id funktioniert nicht, wenn der Datensatz bereits ein Attribut mit der Rolle id enthält.)

Die drei Spezialfunktionen, die in Aggregate nicht enthalten sind, werden gesondert behandelt. Die Untergruppen werden nach dem ausgewählten Attribut sortiert und der Datensatzzähler bzw. die Rangfolge berechnet.

Die Standard-Aggregierungsfunktionen von RapidMiner werden einfach auf jede Subgruppe angewendet, dabei entsteht jeweils eine gruppierte Zeile. Diese wird wieder mit den Originaldaten gejoint.

Einschränkungen

Gegenüber dem SQL-Standard und der in Datenbanken implementierten Funktionalität fehlt noch einiges. Z. B. erlauben Datenbanken bei Funktionen wie SUM die Angabe eines Sortierkriteriums und berechnen dann eine kumulierte Summe für jede Zeile, statt die gleiche Summe wiederholt auszugeben.

Der Prozess ist auch recht langsam, wenn viele Gruppen existieren. Dies ergibt eine große Anzahl von Filterungen in der Schleife. Bei den speziellen Rang- und Zählerfunktionen ist das Problem besonders ausgeprägt, da in jedem Durchlauf ein Groovy-Interpreter gestartet wird, was erhebliche Zeit kostet.

Das Konzept von Window-Definitionen geht in SQL-Datenbanken über Selektion von Gruppen hinaus. Z. B. kann man in SQL auf den vorherigen oder nächsten Datensatz zugreifen (LAG bzw. LEAD), ein fixes Fenster von X Zeilen definieren, und in den Daten „nach vorne“ schauen. Diese Dinge sind gegenwärtig nicht im Prozess eingebaut.

Mögliche Erweiterungen

Es spricht nichts dagegen, einige von den in SQL zur Verfügung stehenden Funktionen wie percent_rank, ntile oder first/last_value einzubauen. Dies wird bei Bedarf sicher passieren.

Die Funktionalität, über ORDER BY das Verhalten der Aggregierungsfunktionen zu ändern (z. B. für kumulierte Summen), erscheint mit RapidMiner-Mitteln nicht einfach. Allerdings ließe sich die kumulierte Summe leicht mit einem weiteren Groovy-Skript implementieren.

Bezugsquelle

Das Projekt kann auf Github heruntergeladen oder ausgecheckt werden. Es enthält Dokumentation und Beispielprozesse. Die Lizenz ist Apache 2.0, somit sind Einsatz und Weiterentwicklung uneingeschränkt möglich. Es ist also ein klassisches Open-Source-Projekt, das von der Beteiligung und Weiterentwicklung durch die Community leben soll.

Window functions in RapidMiner

For powerful database software like PostgreSQL the SQL-99 standard has been implemented for a long time. Window functions are an important part of SQL-99.

Window functions are used for calculating groupwise statistics and measures without actually grouping the result. (This is the difference between Window functions and the well-known aggregation with GROUP BY.) There are many applications: running sums, moving averages, ratios inside a group, groupwise minimum and maximum and so on. Some people consider the introduction of Window functions the beginning of a new era for SQL. I agree with this quite strongly.

RapidMiner doesn’t offer Window functions, yet. Experienced data scientists were able to build this functionality when they had to, using loops, aggregation and join, but these processes are not easy to create and debug.

My goal is to open this functionality for a larger group of potential users and to create a reusable implementation. So I started the rapidminer-windowfunctions project and uploaded the first working version.

All functions built into RapidMiner’s Aggregate operator can be used: sum, average, minimum, maximum etc. Additional functions are row_number, rank and dense_rank.

The project ships with the actual process and a number of example processes. These demonstrate different applications of Window functions on public datasets available in RapidMiner Studio.

Golf dataset with groupwise temperature average and rank by humidity
  • Golf: The process loads the well-known Golf dataset from the Samples repository. For each group defined by the attribute “Outlook”, the average temperature gets calculated. A second attribute ranks the examples by the attribute “Humidity”. Identical values get the same rank.

  • Iris: This is another standard dataset, available from RapidMiner’s Samples repository. The process calculates the average value for the attribute “a3”. This group average is used to filter out examples which differ from the average by more than 10 %.

  • Deals: Another data set built into RapidMiner. This process considers all combinations of Gender and Payment Method as groups and ranks the customers based on their age. Only the 3 oldest customers per group are retained.

This is just a small sample of all the functionality available with Window Functions. Being able to use them easily for building indicators and measures like “transaction counter per customer and month” or “the product’s contribution percentage” will open new possibilities for modelling or reporting.

Implementation

The process checks a few preconditions, for example required parameter macros. It creates new attributes for grouping and identifying examples. The grouping attribute is built from all values given in the groupFields parameter. The identifying attribute needs to be generated in a Groovy script, as Generate Id would fail on too many real-world datasets. (It doesn’t work when the dataset already has an attribute with the role id.)

The three special functions not available in Aggregate are implemented with Groovy scripts. The process sorts the subgroups by the selected attributes and calculates the record counter or the rank.

The implementation of the standard aggregations is simpler: each subgroup is aggregated and the result is joined with the original data.

Limitations

Some functionality is missing compared to the SQL standard and to database systems. For example, in SQL databases, you can use ORDER BY in the window function to specify an ordering and calculate cumulative sums for each row instead of a groupwise sum.

The process is quite slow if many groups exist in the dataset. Having many groups results in a large number of filters in the loop. The performance is especially bad with the special ranking and counter functions because they start a Groovy interpreter in each iteration, which takes a lot of time compared to other operators.

The concept of window definition in SQL databases covers more than just the selection of groups. For example, you can access the previous and next records (LAG and LEAD), define a fixed window of N rows, and look forward in the data. These things are not available in the current version of the process.

Possible extensions

It’s entirely possible to implement more SQL standard functions like percent_rank, ntile and first/last_value. This will happen for sure when there’s a need.

It seems to be hard to change the behaviour of aggregation functions using ORDER BY (e. g. for cumulative sums) with RapidMiner means. However, special cases like the cumulative sum could be implemented with another Groovy script.

Getting the process

You can download the process or check it out on Github. The project also contains documentation and example processes. The license is Apache 2.0, so there are no restrictions on usage or further development. It is intended to become an Open Source project with the participation of and further development by the community.

Absicherung für Cron in RapidMiner Server

(English version)

Seit der Version 7.2 ist der RapidMiner Server in einer eingeschränkten Version frei erhältlich, und es lassen sich sehr nützliche Dinge damit machen.

Eine Kernfunktion ist das geplante Ausführen von Prozessen. Das passiert mit Hilfe von Cron-Ausdrücken, wobei die Implementierung in RM Server die Cron-Syntax vorne um ein Sekunden-Feld erweitert. Das erhöht natürlich die Flexibilität, aber birgt eine Gefahr in sich: wenn man nicht aufpaßt, legt man ganz schnell einen Cron-Job an, der jede Sekunde einen Prozess startet. Das kann auch den besten Server schnell in die Knie zwingen.

Leider ist die Benutzeroberfläche so gestaltet, daß die Standardeinstellungen genau dieses Problem verursachen:

Cron Editor in RapidMiner Studio with default settings
Cron Editor in RapidMiner Studio mit den Standardeinstellungen

Wer intensiv an einem Server arbeitet, wird vielleicht früher oder später die Standardeinstellungen übernehmen. Oder es passiert jemandem in einem Training dieser Fehler. Glücklicherweise gibt es einen Weg, die Datenbank gegen diese Cron-Jobs abzusichern.

Mein RapidMiner Server verwendet eine PostgreSQL-Datenbank im Hintergrund. Die Tabelle qrtz_cron_triggers enthält die definierten Cron-Jobs. Es ist möglich, einen Trigger auf diese Tabelle zu legen, der unerwünschte Eingaben ablehnt oder, noch eleganter, korrigiert.

In PostgreSQL bestehen Trigger aus zwei Teilen: einer Triggerfunktion und dem eigentlichen Trigger. (Dadurch läßt sich eine Funktion für mehrere ähnliche Trigger verwenden, man muß sie nicht jedes Mal neu schreiben.)

Meine Triggerfunktion schaut so aus:


create or replace function check_second_cron() returns trigger
as $func$
begin
  new.cron_expression = regexp_replace(new.cron_expression, 
                                       '^\s*(\*|\d+/\d)\s', 
                                       '0 ');
  return new;
end;
$func$
language plpgsql;

Die Funktion ersetzt im hereinkommenden Cron-Ausdruck unerwünschte Sekundenangaben gegen 0. Eine unerwünschte Angabe ist * (jede Sekunde), die andere hat die Form X/Y (beginnend bei der Sekunde X, alle Y Sekunden). Hier vermeiden wir einstellige Sekundenangaben. Damit kann man einen Prozess alle 10 Sekunden oder seltener laufen lassen, aber nicht häufiger; das reduziert die Gefahr schon wesentlich.

Der reguläre Ausdruck schaut komplex aus, ist aber nicht so schlimm:

^\s* – Am Anfang darf Whitespace (Space oder Tabulator) vorkommen

(\*|\d+/\d) – Entweder ein Stern ODER Ziffer(n) gefolgt von einer Ziffer

\s – Wieder Whitespace

Der Trigger wird dann auf die Tabelle angewendet, und zwar sowohl bei Insert als auch bei Update, und auch nur, wenn der Cron-Ausdruck den unerwünschten Inhalt hat:


drop trigger if exists prevent_second_cron on qrtz_cron_triggers;
create trigger prevent_second_cron
before insert or update 
on qrtz_cron_triggers
for each row
when (new.cron_expression ~ '^\s*(\*|\d+/\d)\s')
execute procedure check_second_cron();

Damit wurde der Server gegen die unabsichtliche oder bösartige Eingabe von zu häufig ausgeführten Cron-Prozessen abgesichert. (Natürlich könnte ein Angreifer auch anders viele Prozesse gleichzeitig starten; dagegen helfen nur Queues.)

 

 

Securing Cron in RapidMiner Server

A slightly limited version of RapidMiner Server is freely available since release 7.2. It is a very useful piece of software.

Scheduled process execution is a core functionality. The execution time and frequency are specified using Cron expressions; however, the implementation in RM Server extends the usual Cron syntax by a leading Seconds field. This gives us more flexibility but is also dangerous: If we aren’t careful, we can easily schedule a process to run every second. This can cause a huge usage even on the fastest server.

Unfortunately, the default settings in the user interface expose exactly this problem:

Cron Editor in RapidMiner Studio with default settings
Cron Editor in RapidMiner Studio with default settings

People working a lot on a server will probably accept the default values, or someone in a training makes a mistake. Fortunately, there’s a way to secure the database against this kind of Cron schedule.

My RapidMiner Server uses a PostgreSQL backend database. The table qrtz_cron_triggers contains the defined Cron jobs. It is easy to create a trigger on this table that rejects undesired input, or fixes it, which I consider more elegant.

Triggers consist of two parts in PostgreSQL: a trigger function and the actual trigger. (This allows us to use the same function in many triggers instead of writing it many times.)

This is my trigger function:


create or replace function check_second_cron() returns trigger
as $func$
begin
  new.cron_expression = regexp_replace(new.cron_expression, 
                                       '^\s*(\*|\d+/\d)\s', 
                                       '0 ');
  return new;
end;
$func$
language plpgsql;

The function replaces unwanted specification of seconds by zero. One kind of unwanted entry is * (every second), the other variant has the form X/Y (start at X seconds, repeat every Y seconds). Here we deny one-digit second entries. This allows us to schedule a process every 10 or more seconds, but not more frequently. This reduces the problem by a large margin.

The regular expression seems complex but it’s not too bad:

^\s* – The beginning of the expression can contain whitespace

(\*|\d+/\d) – A star OR digit(s) followed by / and one digit

\s – Whitespace again

The trigger is placed on the table both for Insert and Update and is executed when the expression has unwanted contents:


drop trigger if exists prevent_second_cron on qrtz_cron_triggers;
create trigger prevent_second_cron
before insert or update 
on qrtz_cron_triggers
for each row
when (new.cron_expression ~ '^\s*(\*|\d+/\d)\s')
execute procedure check_second_cron();

Thus the server was secured against unintentional or malicious creation of Cron processes that run too often. (An attacker could start many processes at a time with other means, of course. This can be avoided with Queues.)

Generic Joins in RapidMiner

Allgemeine Joins in RapidMiner

(English version)

Der letzte Beitrag hat sich mit der Verbesserung von geographischen Joins in RapidMiner beschäftigt. Tatsächlich kann die beschriebene Methode aber auf alle Arten von Joins angewendet werden, die nicht nur für einen kleinen Nutzerkreis interessant sind.

Der eingebaute Join-Operator in RapidMiner erlaubt nur „ist gleich“-Vergleiche. In der Praxis sind aber auch andere Operationen nützlich: z. B. reguläre Ausdrücke, oder „kleiner gleich“.

Der Beispielprozess nutzt generierte Testdaten und wendet eine Liste von regulären Ausdrücken auf sie an. Wie schon bei den geographischen Joins wird dafür ein Scripting-Operator mit einem Groovy-Skript verwendet.

Das Skript muß wie gehabt ziemlich viel Datenverwaltung betreiben, um die resultierende Tabelle aus den beiden Eingangs-Tabelle zu bauen. Bei der Erstellung des Skripts war wichtig, daß es möglichst allgemein verwendbar ist. Das ist in Groovy mit Hilfe der Closure-Syntax möglich: die Join-Funktion wird am Anfang des Skripts als eine Art Konfigurationsvariable festgelegt. Somit sind im Skript nur drei Dinge festzulegen: die beiden Join-Attribute und der Ausdruck, der auf sie anzuwenden ist.

In diesem Beispiel:

es1AttName = "Person";
es2AttName = "regexp";

def joinFunc = { e1, e2 ->
    e1.matches(e2)
}

Die beiden Attributsvariablen aus Example Set 1 und 2 werden am Anfang angegeben. Hier müssen die Attributnamen exakt (inkl. Groß- und Kleinschreibung) angegeben werden.

Die Closure joinFunc nimmt zwei Parameter (e1 und e2) entgegen, das sind die Werte aus den genannten Attributen. Die Funktion prüft in diesem Fall, ob das Person-Attribut dem regulären Ausdruck aus dem zweiten Datensatz entspricht.

Andere Join-Kriterien könnten so lauten:

e1 <= e2 // kleiner-gleich-Vergleich
e1 == e2 || e2 == null // Vergleich mit optional fehlenden Daten

usw.

Für den Vergleich mehrerer Variablen muß das Skript dann doch angepaßt werden.

Das Skript verzichtet aus Performance-Gründen auf eine Prüfung, ob gleichnamige Attribute in beiden Datensätzen existieren. Im Zweifelsfall kann die Eindeutigkeit der Namen mit einem Rename by Replacing vor dem Join-Operator sichergestellt werden.

In SQL verwende ich immer wieder komplexe Joins – jetzt wird Ähnliches auch in RapidMiner möglich sein.

Generic Joins in RapidMiner

The last blog entry presented an improvement of geographic joins in RapidMiner. However, the described method can be applied to all kinds of joins, including those usable for a much larger group of analysts.

The Join operator in RapidMiner supports only the „is equal“ comparison. In reality, other operations like regular expression matching or „less or equal“ can be useful.

The example process uses generated data and joins a list of regular expressions with them. A Groovy script in an Execute Script operator is used, just like in the geographic joins.

Much data wrangling is done in the script as usual to build the resulting table from the incoming example sets. I developed the script with the goal of reusability. The closure syntax in Groovy makes this possible: the join function is specified in a kind of configuration variable at the top of the script. So to reuse the script you just need to set three variables: both join attributes and the expression to apply on them.

This is the configuration part in the example script:

es1AttName = "Person";
es2AttName = "regexp";

def joinFunc = { e1, e2 ->
    e1.matches(e2)
}

The attribute variables from ExampleSet 1 and 2 are given in the first two rows. It is important to specify them exactly (including capitalization).

The closure called joinFunc takes two parameters (e1 and e2), the values of the selected attributes. In this case the function executes a regular expression match on them.

Other possible join criteria:

e1 <= e2 // less-or-equal comparison
e1 == e2 || e2 == null // comparison that allows missing data

and so on.

The script needs to be changed for joins on multiple variables.

For performance reasons the script doesn‘t check for duplicate attribute names in the incoming data sets. If unsure, just use a Rename by Replace operator before the join to make sure the names are unique.

I‘m using complex joins in SQL all the time – it‘s time to make similar things possible in RapidMiner!

Verbesserte geographische Joins in RapidMiner

(English version)

Ich habe früher beschrieben, wie geographische Joins und Filter in RapidMiner mit Hilfe des Cartesian-Product-Operators umgesetzt werden. Dieser Ansatz ist relativ einfach nachvollziehbar und funktioniert mit kleinen Datenmengen ganz gut.

Leider wird bei einem Cartesian Join jede Zeile der beiden hineingehenden Datensätze miteinander kombiniert. Das Ergebnis des kartesischen Produkts hat dann so viele Zeilen wie das Produkt der beiden Datensatzlängen. Natürlich wird da viel Speicher verwendet, und die Schleife mit den gewünschten Berechnungen läuft länger.

Für größere Datenmengen mit jeweils Hunderten oder Tausenden von Zeilen gibt es einen besseren Ansatz. Der Script-Operator kann auch zwei ExampleSets verarbeiten. Mit einem etwas komplexeren Skript kann der Prozess in einer verschachtelten Schleife beliebige Join-Kriterien anwenden, ohne den Speicher mit einem temporären kartesischen Produkt zu belasten.

In diesem Beispielprozess hole ich von Open Data Wien eine Liste von über 28.000 Straßenabschnitten in Wien (als Linien) sowie die 23 Wiener Bezirke (als Flächen). Die Aufgabe ist, für jeden Bezirk die Straßen zu bestimmen, die in ihm liegen. Dafür wird die GeoScript-Funktion contains() verwendet.

Bei der Cartesian-Join-Methode werden für jede Zeile zwei als String gespeicherte Geo-Objekte konvertiert. Dies ist aufwändig, aber kaum zu vermeiden. Mit der verbesserten Methode ist es einfach, ein Array von konvertierten Geo-Objekten zu erstellen und indexbasiert abzufragen.

Das Ergebnis ist ein Prozess, der mit relativ geringen Speicher-Anforderungen und einer akzeptablen Geschwindigkeit auch größere Datenmengen verknüpfen kann.

Der Nachteil ist ein etwas komplexeres Skript, das jedoch nach der ersten Entwicklung leicht an neue Anforderungen angepaßt werden kann.

Der größere Aufwand lohnt sich aber auf jeden Fall. Der Beispielprozess ordnet auf meinem Rechner die 28.309 Straßen den 23 Bezirken in ca. 14 Sekunden zu. Ein versuchsweise erstellter alternativer Prozess, der mit Cartesian Join arbeitet, läuft über 20 Minuten, also fast 90 mal langsamer, und braucht dabei natürlich deutlich mehr Speicher.

Das Skript erstellt ein komplett neues ExampleSet, das alle Attribute aus den beiden hereinkommenden ExampleSets übernimmt (doppelte Attributnamen sollten vorher korrigiert werden). Das Ergebnis enthält nur die Datensätze, auf die die Bedingung zutrifft, es ist kein nachgeschalteter Filter notwendig.

Um das Skript möglichst wiederverwendbar/flexibel zu machen, werden alle prozess-spezifischen Teile am Anfang als Variablen konfiguriert. Das sind einerseits die Namen der beteiligten Attribute, andererseits aber auch die Join-Funktion. Da Groovy sogenannte Closures (Funktionsvariablen) anbietet, können wir am Anfang des Skripts die verwendete Funktion eingeben – die hier konfigurierte Funktion wird später im Join verwendet.

Konkret sieht die Definition so aus:


def joinFunc = { a1, a2 -> a1.contains(a2) }

Das bedeutet: eine Funktionsvariable namens joinFunc mit den Parametern a1 und a2 erstellen. Hinter dem -> erscheint der Inhalt der Funktion, in diesem Fall der Aufruf a1.contains(a2). Der Rückgabewert sollte ein Boolean sein; beim Rückgabewert True werden die beteiligten Zeilen ins Ergebnis ausgegeben. Der fixe Teil des Skripts ruft dann irgendwo in der Schleife die Funktion mit joinFunc(a1, a2) auf.

Diese Konstruktion hilft uns, das Skript einfacher wiederzuverwenden: Sollten wir einmal statt contains() etwa intersects() benötigen, braucht nur der Konfigurationsblock geändert zu werden. Wir müssen nicht mehr nach dem Funktionsaufruf irgendwo in den Tiefen des Skripts zu suchen.

Geographic joins in RapidMiner, improved

Earlier I described geographic joins and filters in RapidMiner. The processes used the Cartesian Product operator. This approach is quite simple and easy to understand, and it works well with smaller data sets.

Cartesian Join combines each row of the incoming tables. The result has as many rows as the number of rows of both data sets multiplied together. This uses a lot of memory and the loop that calculates the results takes longer.

There‘s a better approach for larger data sets with hundreds or thousands of rows. The Execute Script operator can work with two incoming ExampleSets. A slightly more complex script can use nested loops to apply almost arbitrary join criteria on the data, without filling the memory with the cartesian product.

This example process fetches over 28,000 street segments as lines and 23 districts of Vienna as polygons from the Vienna Open Data server. The task is finding the streets inside each district. So this is a join based on the GeoScript function contains().

The Cartesian Join needs to convert two strings to Geometry objects in each line of the combined data set. This is slow and hard to avoid. The improved method makes it easy to create an array of converted Geometry objects and use them for further lookups.

The resulting process uses less memory and has an acceptable runtime even with large inputs. This comes at the cost of a more complex script; however, it should be easy to change the script for future requirements.

The effort is really worth it. On my computer the example process joins 28,309 streets with 23 districts in about 14 seconds. A variant of the process that uses Cartesian Join runs for over 20 minutes (about 90 times slower) and uses a lot more memory.

The script creates a new ExampleSet that combines all attributes of both incoming ExampleSets. (If you have duplicate attribute names, rename them before sending the data to the script.) The result contains only records that match the condition, no additional filtering is necessary.

In order to make the script as reusable as possible, all process specific parts are configured at the top in variables. There are variables for the join attributes‘ names and also for the join function. Groovy offers so-called Closures (function variables), so we can specify the function to use in the configuration block. The configured function will be used for joining later in the loop.

This is how the definition looks like:


def joinFunc = { a1, a2 -> a1.contains(a2) }

Translated: create a function variable called joinFunc created with parameters a1 and a2. The body of the function is specified after ->, in this case a1.contains(a2). The return value of the function should be a Boolean – if the value is True, the current rows will be put into the result example set.

The fixed part of the script calls this function using joinFunc(a1, a2) later in the loop.

With this construction we can reuse the script easily: If we need intersects() instead of contains() at some point in the future, we just change it in the script configuration, without having to analyze and change the code below.

Linuxwochen 2016 Wien: Citizen Data Science

Wie schon einige Male halte ich wieder einen Vortrag bei den Linuxwochen Wien. Dieses Jahr heißt mein Thema „Citizen Data Science“.

Der Begriff „Citizen Data Scientist“ wurde von großen Beratungsfirmen geprägt. Sie verstehen darunter Mitarbeiter in Unternehmen, die keine Data-Scientist-Ausbildung haben, aber trotzdem analytisch arbeiten.

Ich möchte mich allerdings auf mein Verständnis von „Citizen“– wir alle, nicht unbedingt in einem Unternehmenskontext – konzentrieren.

Im Vortrag geht es darum, was man sich unter Data Science vorstellen kann, welche Werkzeuge und Methoden es gibt, und wie man mit frei verfügbarer Software Daten holen, zusammenführen, verarbeiten und analysieren kann.

Einige Themen: Open Data und Web-APIs; Datenbanken; Software für Analytik.

Hier sind die Vortragsfolien.

RapidMiner-Training in Wien; Sprechstunde

Die nächsten RapidMiner-Trainings in Wien (Basics 1 + 2: Einführung in Datenvorverarbeitung und Predictive Analytics/Data Mining mit RapidMiner) halte ich ab 9. Mai 2016. Es sind jeweils 2 Tage, also insgesamt 4.

Die Anmeldung ist hier möglich. Wir sind wieder bei Data Technology zu Gast.

Seit einiger Zeit gibt es „RapidMiner-Sprechstunden”, bei denen ein Experte ein bestimmtes Thema per Webcasting vorstellt. Während der Sprechstunde kann man auch Fragen stellen; die Aufzeichnung ist dann auf Youtube verfügbar.

Ich werde am 20. April und dann am 1. Juni die Sprechstunde leiten.

Am 20. 4. ist „Geographic Processing and Visualization“ das Thema (auf Basis meiner Blogserie GIS in RapidMiner), am 1. 6. dann „Access Google Analytics using RapidMiner“.

 

Simulation mit RapidMiner

(English version)

Die Simulation von mathematisch beschreibbaren Prozessen ist für viele Anwendungen nützlich. Die „Monte-Carlo-Simulation“ ist eine elegante Methode, verschiedene Probleme zu lösen, etwa die Berechnung der Kreiszahl Pi.

RapidMiner wird zwar nicht unbedingt als Simulationswerkzeug beworben, aber auch diese Aufgabe läßt sich mit etwas Know-How leicht darin lösen. Und was liegt näher als die „Monte-Carlo-Simulation“ am Beispiel von (französischem) Roulette zu demonstrieren?

Roulette läßt sich einfach beschreiben: Die Spieler setzen auf verschiedene Bereiche eines Tisches. Diese Bereiche decken unterschiedlich große Teile des Zahlenraums zwischen 0 und 36 ab. Die Wahrscheinlichkeit, daß ein Zahlenfeld gewinnt, liegt bei 1/37; bei größeren Bereichen (z. B. vier Zahlen, sechs Zahlen, ein Drittel der Zahlen von 1-36, eine Farbe) bei Mehrfachen davon. Der Gewinn ist bei einzelnen Zahlen das 35-Fache des Einsatzes (zusätzlich zum Einsatz, den man behält), und nimmt bei größeren Bereichen proportional zum Risiko ab. Z. B. erhält man, wenn man etwa auf Rot gesetzt hat und die Kugel auf einem roten Feld landet, den Einsatz noch einmal zurück. Da auch das Feld 0 existiert, das von den meisten möglichen Einsätzen nicht abgedeckt ist, verdient die Bank auch manchmal Geld. Roulette ist wahrscheinlich das „fairste“ reine Glücksspiel, das man spielen kann. (Deswegen gibt es wohl auch „Amerikanisches Roulette“ mit einem zweiten Null-Feld – Die Kasinos in den USA haben sich mit 1/37 Gewinn nicht zufriedengegeben.)

Simulationsprozess

Der hier verfügbare Prozess simuliert wiederholte Besuche im Kasino mit einigen Einstellungen. Die Einstellungen lassen sich im Prozesskontext (View/Show Panel/Context) in Form von Makros setzen. Sie sind auch direkt im Prozess beschrieben.

Man legt die Anzahl der Besuche fest und die Anzahl der Spiele pro Besuch (solange man noch Geld übrig hat). Es läßt sich auch ein Betrag angeben, bei dessen Erreichen man freiwillig aufhört zu spielen. (Z. B. wenn man das anfängliche Guthaben verdoppelt hat.)

Die eigene Spielweise läßt sich im Makro „Risk“ einstellen. Hier legt man das gewünschte Risiko fest: von 2 (rot/schwarz, gerade/ungerade, 1-18/19-36) bis 36 (einzelne Zahl).

Der Prozess besteht aus zwei verschachtelten Schleifen (Loop Visits und Loop Bets) und danach etwas Datenaufbereitung für die Darstellung der Ergebnisse.

Die tatsächliche Berechnung einer Spielrunde findet in „Calculate bet results“ (Generate Attributes) statt. Aus dem Einsatz und dem Risiko wird der Gewinn (Einsatz + gewonnener Betrag) oder der Verlust (der Einsatz) berechnet; daraus dann das neue Spielguthaben.

Nach der Ausführung der äußeren Schleife werden Kennzahlen errechnet.

Aggregierte Ergebnisse der Simulation
Aggregierte Ergebnisse der Simulation

average(VisitWonPct): Anteil des Einsatzes, der durchschnittlich gewonnen oder verloren wurde. (Realistischerweise eher verloren, ausgedrückt durch eine negative Zahl.)

average(VisitReachedGoal): Anteil der Casino-Besuche, bei denen man das Ziel-Guthaben (z. B. 150 € bei einem Anfangsguthaben von 100 €) erreicht hat.

average(VisitLostEverything): Anteil der Casino-Besuche, bei denen man das gesamte Anfangsguthaben verloren hat.

average(VisitWonAmount): Durchschnittlich gewonnener oder verlorener Betrag.

Zusätzlich lassen sich die Verläufe der einzelnen Besuche grafisch darstellen:

Grafische Darstellung der einzelnen Spielverläufe
Grafische Darstellung der einzelnen Spielverläufe

Dafür habe ich das Ergebnis „Pivot for series plot“ geöffnet, die Charts aktiviert, den Chart-Typ Series ausgewählt, die BetNr als Index-Dimension festgelegt und alle CurrentAmount-Spalten für die Plot Series markiert.

Jede Linie zeigt den Verlauf eines Casinobesuchs (pro Besuch eine Farbe). Die X-Achse ist die Spielrunde, die Y-Achse das Guthaben, das man nach dieser Runde hat. Es ist gut sichtbar, daß es Besuche gab, in denen man über 30 Runden nur verloren hat! Oben ist das Feld durch die Besuche begrenzt, in denen man vor Ablauf der geplanten Spielrunden den Zielbetrag erreicht hat.

Mit diesem Simulationsprozess, der auch mit RapidMiner Studio Basic (gratis und ohne Registrierung nutzbar) funktioniert, läßt sich gut feststellen, wie die Chancen im Casino stehen, wenn man mit einer „Strategie“ spielt. Wenig überraschend ist das Ergebnis überwiegend negativ.

Der Prozess ließe sich leicht für andere Arten von Spielen und generell andere Zwecke adaptieren. Eine Monte-Carlo-Simulation zur Annäherung von Pi wäre z. B. mit der gleichen Struktur möglich.

Simulation in RapidMiner

There are many applications for simulation of processes that can be described mathematically. The Monte Carlo simulation is an elegant method for solving different problems like calculating Pi.

RapidMiner is not advertised as a simulation tool, but with a bit of knowledge you can easily solve this task in it. So how about demonstrating Monte Carlo simulation with the example of French roulette?

The game of roulette is easy to describe. Players bet on different areas of a table. The areas cover smaller or larger sets of the numbers between 0 and 36. The probability of winning with a single number is 1/37; when betting on a larger area (e. g. four numbers, six numbers, one third of the numbers between 1 and 36, one color) it is a multiple of that. The won amount when guessing a number correctly is the 35 times the bet amount (and the player keeps the bet), and it decreases proportionally with the risk. For example, if you bet on red and the ball lands on a red field, you win an equal amount to your bet.

There is also a field with 0 that is not covered by most of the bets, so the bank also wins sometimes. Roulette is probably one of the most „fair“ games of luck available. (That’s probably the reason for the existence of American roulette that has a second 0 field: The US casinos weren’t satisfied with winning just 1/37 of the players‘ money.)

Simulation process

The process available here simulates repeated casino visits with a few settings. You can manipulate the settings in the process context (View menu/Show Panel/Context) in the Macros area. The process contains a description of each setting.

You can specify the number of visits and the betting rounds per visit (as long as there’s money left in the current visit). It is also possible to specify an amount that is enough to leave the casino before the specified number of rounds. (E. g. when you doubled the original amount.)

You can set a „style of gambling“ with the macro „Risk“. Here you specify the risk you’d like to try: it starts at 2 (red/black, even/odd, 1-18/19-36) and ends at 36 (betting on one number).

The process contains two loops, the first one (Loop Visits) holding the second one (Loop Bets) inside. After those there is some processing for displaying results.

The calculation of one betting round happens in „Calculate bet results“ (Generate Attributes). The amount lost or won in the round is calculated from the bet amount and the risk. This results in a change of the current amount.

After finishing the outer loop, the process calculates a few summary results.

Aggregated simulation results
Aggregated simulation results

average(VisitWonPct): Portion of the original amount that was lost or won in the average case. (Realistically, lost: this is expressed with a negative percentage.)

average(VisitReachedGoal): Portion of visits when reaching the desired amount (e. g. 150 € after starting with 100 €).

average(VisitLostEverything): Portion of visits when you lost everything.

average(VisitWonAmount): Average amount won or lost.

In addition to the numbers you can display each visit graphically:

Chart of each visit's progression
Chart of each visit’s progression

Open the „Pivot for series plot“ result, go to Charts, select Series, select BetNr as the Index dimension and mark all CurrentAmount attributes as Plot Series.

Each line describes the course of a casino visit (one color per visit). The X axis is the betting round, the Y axis the available amount after the round. It is easy to see that in several visits up to 30 rounds have been lost! The upper limit is the specified „leaving amount“ that was reached before the number of specified rounds.

This simulation process even works in RapidMiner Studio Basic (available for free without registration). You can easily determine your chance of winning in the casino playing your „strategy“. It’s not a big surprise that the result is mostly negative.

It should be easy to change the process to simulate other games or events. For example, you could estimate Pi using the same process structure.