Neu in

Java-Integration

Jetzt können Sie Java-Code direkt in Stata einbetten und ausführen. Zuvor konnten Sie Java-Plugins in Stata erstellen und verwenden, aber dazu mussten Sie Ihren Code kompilieren und in einer JAR-Datei bündeln. Das können Sie immer noch tun, aber Sie können Ihren Java-Code jetzt auch direkt in do-Dateien, ado-Dateien oder sogar interaktiv schreiben. Der Java-Code, den Sie schreiben, wird sofort kompiliert – ein externer Compiler ist nicht erforderlich! Möglicherweise können Sie sogar parallelisierten Code schreiben, um die Vorteile mehrerer Kerne zu nutzen.
Das mitgelieferte Stata Function Interface (sfi) Java-Paket bietet eine bidirektionale Verbindung zwischen Stata und Java.
Stata bündelt das Java Development Kit (JDK) mit seiner Installation, sodass keine zusätzliche Einrichtung erforderlich ist. Diese Version von Stata enthält Java 11, die aktuelle Version mit Langzeitunterstützung (LTS).

Höhepunkte

  • Java-Integration mit Stata
    • Java interaktiv (wie JShell) aus Stata heraus verwenden
    • Java-Code in do-Dateien einbetten
    • Einbetten von Java-Code in ado-Dateien
    • Kompilieren von Java-Code im laufenden Betrieb ohne externe Programme
  • Stata Function Interface (sfi) Java-Paket
    • Bidirektionale Verbindung zwischen Stata und Java
    • Zugriff auf Stata-Datensätze, Frames, Makros, Skalare, Matrizen, Wertelabels, Merkmale, globale Mata-Matrizen, Datums- und Zeitwerte und mehr aus Java

Zeigen Sie, wie es funktioniert

Beispiel 1: Java interaktiv aufrufen

Wenn Sie mit JShell vertraut sind, wird das Folgende für Sie ähnlich aussehen.

. java:
java (type end to exit and /help for help)
java> System.out.println("Hello, Java!"); Hello, Java! java>

In diesem Beispiel haben wir Java aufgefordert, „Hallo, Java!“ zu drucken. Wir können Java interaktiv über das Befehlsfenster von Stata aufrufen.

Beispiel 2: Java-Code in eine do-Datei einbetten

Wir können Java in eine do-Datei einbetten, genau wie wir es mit Mata- und Python-Code tun. Platzieren Sie den Java-Code einfach zwischen java: und end.

--------------------- do-file (begin)----------------------------
java:
ArrayList<String> coffeeList = new ArrayList<>(Arrays.asList(
        "Latte", "Mocha", "Espresso", "Cappuccino"));

  Collections.sort(coffeeList);
  for ( String coffee : coffeeList ) {
        SFIToolkit.displayln(coffee) ;
  }
  end // leave Java
--------------------- do-file (end) -----------------------------

--------------------- output (begin) ----------------------------
. java:
java (type end to exit and /help for help)
java> ArrayList<String> coffeeList = new ArrayList<>(Arrays.asList( ...> "Latte", "Mocha", "Espresso", "Cappuccino")); coffeeList ==> [Latte, Mocha, Espresso, Cappuccino] java> java> Collections.sort(coffeeList); java> for ( String coffee : coffeeList ) { ...> SFIToolkit.displayln(coffee) ; ...> } Cappuccino Espresso Latte Mocha java> end // leave Java
--------------------- output (end) -------------------------------

Wir haben eine ArrayList mit vier verschiedenen Kaffeegetränken („Latte“, „Mocha“, „Espresso“, „Cappuccino“).
Wir haben die ArrayList mit Collections.sort() sortiert und dann die sortierte Liste ausgedruckt, eine in jeder Zeile.
Cappuccino
Espresso
Milch
Mokka

Beispiel 3: Java-Code in eine ado-Datei einbetten

Im Gegensatz zu Python neigen Java-Bibliotheken dazu, eine Implementierung auf niedrigerer Ebene zu haben, was bedeutet, dass Sie möglicherweise etwas mehr Code schreiben müssen, um das zu tun, was Sie wollen. Dies führt jedoch oft zu größerer Flexibilität und Leistung. Eine der Stärken von Java sind die umfangreichen APIs, die mit der Java Virtual Machine ausgeliefert werden. Weitere Informationen finden Sie im Java Development Kit. Es sind auch viele nützliche Bibliotheken von Drittanbietern verfügbar.
Nehmen wir an, dass der egen-Befehl von Stata nicht bereits über eine rowmean-Funktion verfügte. Wir könnten die Java-Integration verwenden, um einen Stata-Befehl mit dieser Funktionalität zu schreiben.

--------- rowmean.ado (begin) ------------------------------------------------
program rowmean
    version 17
    syntax varlist [if] [in], GENerate(string)
    confirm new variable `generate'
    preserve
    quietly generate `generate' = .
    java `varlist' `if' `in' : Demo.rowmean("`generate'")
    restore, not
end

java:
import java.util.stream.LongStream;
public class Demo {
    public static void rowmean(String newvar) {
        long obs1 = Data.getObsParsedIn1(); // get observations for in range
        long obs2 = Data.getObsParsedIn2(); // get observations for in range
        int varCount = Data.getParsedVarCount();
        int idxStore = Data.getVarIndex(newvar);

        // loop over observations
        LongStream.rangeClosed(obs1, obs2).forEach(obs -> {
            double sum = 0;
            long count = 0;
            if (!Data.isParsedIfTrue(obs)) {
                return; // skip iteration of lambda expression
            }

            // loop over each variable
            for (int i = 1; i <= varCount; i++) {
                final int var = Data.mapParsedVarIndex(i);
                if (Data.isVarTypeString(var)) continue; // skip if string

                double value = Data.getNum(var, obs);
                if (Missing.isMissing(value)) continue;
                sum += value; count++;
            }
            Data.storeNumFast(idxStore, obs, sum/count);
        });
    }
}
end // end Java block
---------- rowmean.ado (end) -------------------------------------------------

Wir packen unseren Java-Code direkt in unsere ado-Datei. Er wird während des Ladens und Ausführens der ado-Datei kompiliert.
Lassen Sie uns einige Beispieldaten einrichten.

---------- setup-data.do (begin) ----------------------------
clear
set seed 12345
set obs 5000000           // create five million observations
forvalues i = 1/10 {      // generate ten variables
    gen v`i' = runiform()
}
---------- setup-data.do (end) -------------------------------

Führen wir den Befehl aus.

. rowmean v*, gen(rmean)

Das lief in 3,2 Sekunden. Das ist nicht schlecht. Können wir es schneller machen? Und ob wir das können! Wir können die Vorteile unserer mehreren Prozessoren nutzen. Der Rechner, auf dem ich teste, ist ein I7-5820K und hat 6 CPU-Kerne. Eine weitere Optimierung, die wir vornehmen können, ist, dass wir Stata nicht wiederholt nach jedem Variablenindex fragen. Stattdessen können wir diese Informationen einmal abrufen und zwischenspeichern.

Beispiel 4: Schneller machen

Hier ist unser verbesserter Befehl:

--------- rowmean.ado (begin) ------------------------------------------------
program rowmean
    version 17
    syntax varlist [if] [in], GENerate(string)
    confirm new variable `generate'
    preserve
    quietly generate `generate' = .
    java `varlist' `if' `in' : Demo.rowmean("`generate'")
    restore, not
end

java:
import java.util.stream.LongStream;
public class Demo {
    public static void rowmean(String newvar) {
        long obs1 = Data.getObsParsedIn1(); // get observations for in range
        long obs2 = Data.getObsParsedIn2(); // get observations for in range
        int varCount = Data.getParsedVarCount();
        int idxStore = Data.getVarIndex(newvar);

        // cache the variable indexes
        ArrayList<Integer> varmap = new ArrayList<Integer>();
        for (int i = 1; i <= varCount; i++) {
            int idx = Data.mapParsedVarIndex(i);
            if (!Data.isVarTypeString(idx)) {
                 varmap.add(idx);
            }
        }

        // loop over observations
        LongStream.rangeClosed(obs1, obs2).parallel().forEach(obs -> {
            double sum = 0;
            long count = 0;
            if (!Data.isParsedIfTrue(obs)) {
                return; // skip iteration of lambda expression
            }

            // loop over each variable
            for (int var : varmap) {
                double value = Data.getNum(var, obs);
                if (Missing.isMissing(value)) continue;
                sum += value; count++;
            }
            Data.storeNumFast(idxStore, obs, sum/count);
        });
    }
}
end // end Java block
---------- rowmean.ado (end) -------------------------------------------------

Ist es dadurch schneller geworden? Ja, das hat es! Unsere ursprüngliche Zeitmessung betrug 3,2 Sekunden. Diesmal, mit dem gleichen Datensatz, wurde der Befehl in 0,79 Sekunden beendet. Der größte Teil der Verbesserung kam dadurch zustande, dass unser Code parallel ausgeführt wurde. Wenn Sie nicht genau hinsehen, bemerken Sie vielleicht nicht die Änderung, die wir vorgenommen haben. Da wir „LongStream“ verwenden, um eine Schleife über die Beobachtungen zu ziehen, können wir Java bitten, dies parallel auszuführen, indem wir die Methode parallel() vor foreach() im Lambda-Ausdruck aufrufen. Denken Sie daran, dass das Schreiben von parallelem Code viele Fallstricke hat und bei komplizierteren Problemen möglicherweise nicht einfach ist. Und für andere Probleme ist die Parallelisierung möglicherweise nicht von Vorteil.