Nouveau dans 

Intégration de Java

Vous pouvez désormais intégrer et exécuter du code Java directement dans Stata. Auparavant, vous pouviez créer et utiliser des plugins Java dans Stata, mais cela vous obligeait à compiler votre code et à le regrouper dans un fichier JAR. Vous pouvez toujours le faire, mais vous pouvez maintenant aussi écrire votre code Java directement dans des do-files, ado-files, ou même de manière interactive. Le code Java que vous écrivez se compile à la volée – un compilateur externe n’est pas nécessaire ! Vous pouvez même être en mesure d’écrire du code parallélisé pour tirer parti de plusieurs cœurs.
Le paquet Java Stata Function Interface (sfi) inclus fournit une connexion bidirectionnelle entre Stata et Java.
Stata intègre le kit de développement Java (JDK) dans son installation, ce qui évite toute configuration supplémentaire. Cette version de Stata inclut Java 11, qui est la version actuelle de support à long terme (LTS).

Points forts

  • Intégration de Java avec Stata
    • Utilisez Java de manière interactive (comme JShell) à partir de Stata.
    • Intégrer du code Java dans les fichiers do
    • Intégrer du code Java dans les fichiers ado
    • Compilation du code Java à la volée sans programmes externes
  • Paquet Java Stata Function Interface (sfi)
    • Connexion bidirectionnelle entre Stata et Java
    • Accédez aux ensembles de données Stata, aux cadres, aux macros, aux scalaires, aux matrices, aux étiquettes de valeur, aux caractéristiques, aux matrices globales Mata, aux valeurs de date et d’heure, etc. à partir de Java.

Voyons comment cela fonctionne

Exemple 1 : Appeler Java de manière interactive

Si vous êtes familier avec JShell, ce qui suit vous semblera similaire.

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

Dans cet exemple, nous avons demandé à Java d’imprimer « Hello, Java ! ». Nous pouvons appeler Java de manière interactive à partir de la fenêtre de commande de Stata.

Exemple 2 : Incorporer du code Java dans un fichier do

Nous pouvons intégrer Java dans un fichier do, tout comme nous le faisons avec le code Mata et Python. Il suffit de placer le code Java entre java : et 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) -------------------------------

Nous avons une ArrayList contenant quatre boissons au café différentes (« Latte », « Mocha », « Espresso », « Cappuccino »).
Nous avons trié la ArrayList à l’aide de Collections.sort(), puis nous avons imprimé la liste triée, une par ligne.
Cappuccino
Espresso
Lait
Moka

Exemple 3 : Intégrer du code Java dans un fichier ado

Contrairement à Python, les bibliothèques Java ont tendance à avoir une implémentation de plus bas niveau, ce qui signifie que vous devrez peut-être écrire un peu plus de code pour faire ce que vous voulez. Toutefois, cela permet souvent d’obtenir une plus grande flexibilité et de meilleures performances. L’une des forces de Java réside dans ses API étendues, qui sont regroupées avec la machine virtuelle Java. Pour en savoir plus, consultez le kit de développement Java. Il existe également de nombreuses bibliothèques tierces utiles.
Supposons que la commande egen de Stata ne dispose pas déjà d’une fonction rowmean. Nous pourrions utiliser l’intégration Java pour écrire une commande Stata avec cette fonctionnalité.

--------- 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) -------------------------------------------------

Nous mettons notre code Java directement dans notre fichier ado. Il se compile à la volée lorsque le fichier ado est chargé et exécuté.
Configurons un échantillon de données.

---------- 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) -------------------------------

Exécutons la commande.

. rowmean v*, gen(rmean)

Ça a duré 3,2 secondes. Ce n’est pas mal. On peut le rendre plus rapide ? Bien sûr que oui ! Nous pouvons tirer parti de nos multiples processeurs. La machine sur laquelle je teste est un I7-5820K et possède 6 cœurs de CPU. Une autre optimisation que nous pouvons faire est de ne pas demander à Stata de manière répétée l’index de chaque variable. Au lieu de cela, nous pouvons obtenir cette information une fois et la mettre en cache.

Exemple 4 : accélérer le processus

Voici notre commande améliorée :

--------- 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) -------------------------------------------------

Est-ce que ça l’a rendu plus rapide ? Bien sûr que oui ! Notre temps initial était de 3,2 secondes. Cette fois-ci, en utilisant le même ensemble de données, la commande s’est terminée en 0,79 seconde. La plupart de l’amélioration est due à l’exécution de notre code en parallèle. Si vous ne regardez pas attentivement, vous ne remarquerez peut-être pas le changement effectué. Comme nous utilisons « LongStream » pour boucler sur les observations, nous pouvons demander à Java de l’exécuter en parallèle en invoquant la méthode parallel() avant foreach() dans l’expression lambda. Gardez à l’esprit que l’écriture de code parallèle comporte de nombreux pièges et peut ne pas être facile pour les problèmes plus compliqués. Et pour d’autres problèmes, la parallélisation peut ne pas être bénéfique.