JMP 18 bringt leistungsstarke neue Funktionen für die Python-Integration. Dazu gehören eine eingebettete Version von Python 3.11, Unterstützung bei der Paketinstallation, ein Python-fähiger Skripteditor, der reine Python-Skripte ausführen kann, ein JMP-Importpaket , das Python-Zugriff auf die JMP-Funktionalität bietet, verbesserte Fehlerberichterstattung und Unterstützung für Skriptindizes, die die neue Funktionalität in einer Kategorie „Python“ dokumentieren und Beispiele dafür bereitstellen.

Der Fokus liegt hier speziell auf dem jmp.DataTable-Objekt und den Änderungen, die JMP 18 in Bezug auf Python Send (dt) und Python Get (df) vorgenommen hat. Wobei „dt“ eine Datentabelle und „df“ ein pandas.DataFrame ist.
JMP 14-17 bot die Möglichkeit, eine Kopie einer JMP-Datentabelle mit dem JSL-Befehl Python Send(dt); in die Python-Umgebung zu übertragen. Im Hintergrund funktionierte dies so, dass JMP die Datentabelle in eine temporäre CSV-Datei im Dateisystem schrieb und dann Pandas aufrief, um die CSV-Datei von der Festplatte in einen DataFrame zu lesen. Dies funktioniert bei kleinen Dateien einwandfrei, ist jedoch für größere Datenmengen nicht geeignet. Erstens besteht das Problem, dass das Dateisystem zur Datenübertragung verwendet werden muss. Zweitens kann es beim Wechsel von Binär zu Text und dann wieder zurück zu Binär zu Genauigkeitsverlusten kommen. Außerdem ist die CSV-Datei normalerweise wesentlich größer als die ursprünglichen Binärdaten. Und schließlich haben Sie in Python eine Kopie der Daten, aber keine Möglichkeit, die ursprüngliche Tabelle zu bearbeiten. Wenn Sie mit Python verarbeitete Daten mit Python Get(df); zurück in JMP bringen, wird genau der gleiche Vorgang umgekehrt ausgeführt. Python Get(df); wurde aus Kompatibilitätsgründen unverändert gelassen.
Neu in 18: Python Get ( dt ); ruft den Datentabellenverweis auf JSL der tatsächlichen Tabelle ab. Dies ist ein Zero-Copy-Vorgang. Python Send( dt ); erstellt daher keinen pandas.DataFrame mehr. In Python erhalten Sie ein jmp.DataTable-Objekt, das eine Live-Referenz auf die JMP-Datentabelle. Auch dies ist ein Zero-Copy-Vorgang. Er ermöglicht den direkten Zugriff von Python aus, um auf die JMP-Datentabelle zuzugreifen, sie zu bearbeiten und zu ändern. Sie können ein jmp.DataTable-Objekt von Python aus erstellen und dabei eine neue JMP-Tabelle erstellen.
Unten sehen Sie ein JSL-Skript, das die Funktionen von JMP 18.0 nutzt, um einer JMP-Datentabelle eine Spalte hinzuzufügen, die das Verhältnis zweier Spalten aus Python darstellt. Führen Sie dies live auf der aktuellen Datentabelle aus.
// BigClass_hw_ratio.jsl
// Description: Show JMP 18 Python integration features
//
Names Default to Here(1);
dt = Open("$SAMPLE_DATA/Big Class.jmp");
Python Send(dt);
Python Submit("\[
import jmp
# print some information on data table properites
print(dt.__class__)
print(dt.name)
print(f'nrows: {dt.nrows}')
print(f'ncols: {dt.ncols}')
# data table object columns may be indexed either by
# column name or index. Note: slice is only supported
# when using indexes. Slice on both columns and rows is supported.
for x in dt[0:]:
print( x.name )
# As is Python convention, indexing is 0-based
print( dt['name'][0] )
# create a new column based on data from the original data.
# Because JMP is column centric, the access to
# jmp.DataTable object is [column][row]
dt.new_column('H/W ratio', jmp.DataType.Numeric)
for idx in range(dt.nrows):
dt['H/W ratio'][idx] = dt['height'][idx] / dt['weight'][idx]
]\");

Das Ergebnis ist die Protokollausgabe:
/*:
Große Klasse Anzahl Reihen: 40 Anzahl Gruppen: 5 Name Alter Geschlecht Größe Gewicht KATIE 0
Lassen Sie uns nun zeigen, wie man direkt aus Python heraus arbeitet. Unten sehen Sie ein Python-Skript, das „Big Class.jmp“ öffnet, aus dem Objekt jmp.DataTable einen Pandas-Datenrahmen im Speicher erstellt, „Big Class“ schließt, dann aus dem DataFrame eine neue JMP-Datentabelle erstellt und schließlich in JSL die Spaltennamen der aktuellen Datentabelle druckt. Die aktuelle Tabelle ist die, die gerade in Python erstellt wurde.

# dt2pandas2dt.py
# Description: Example showing creating a pandas.DataFrame from a .jmp file,
# then creating a new data table from the data frame.
# This process happening live and in-memory.
#
import jmp
import numpy as np
import pandas as pd
from pandas.api.types import is_object_dtype, is_numeric_dtype, is_bool_dtype, is_string_dtype
# creating a Pandas dataframe from a JMP DataTable directly
dt = jmp.open(jmp.SAMPLE_DATA + "Big Class.jmp")
# create pandas DataFrame from Big Class
df = pd.DataFrame()
for idx in range( len(dt) ):
df[ dt[idx].name ] = np.array( dt[idx] )
print(df)
# we can close dt since we no longer need it open, we
# have everyting in df
dt.close(save=False)
# create JMP DataTable from pandas DataFrame
dt2 = jmp.DataTable('BC2', df.shape[0])
# get the column names from the data frame
names = list(df.columns)
# loop across the columns of the data frame
for j in range( df.shape[1] ):
# check if the coulumn data type is string or numeric
if is_string_dtype(df[ names[j] ] ):
dt2.new_column(names[j], jmp.DataType.Character )
else:
dt2.new_column(names[j], jmp.DataType.Numeric )
# populate the JMP column with data
dt2[j] = list(df.iloc[:,j])
# Just as JSL has a Python Submit(); to execute Python code
# the jmp object has a run_jsl() command to execute JSL from Python
jmp.run_jsl('''
Names Default to Here(1);
Current Data Table() << Get Column Names;
''')
Protokollausgabe haben
/*:
name age sex height weight
0 KATIE 12.0 F 59.0 95.0
1 LOUISE 12.0 F 61.0 123.0
2 JANE 12.0 F 55.0 74.0
3 JACLYN 12.0 F 66.0 145.0
4 LILLIE 12.0 F 52.0 64.0
5 TIM 12.0 M 60.0 84.0
6 JAMES 12.0 M 61.0 128.0
7 ROBERT 12.0 M 51.0 79.0
8 BARBARA 13.0 F 60.0 112.0
9 ALICE 13.0 F 61.0 107.0
10 SUSAN 13.0 F 56.0 67.0
11 JOHN 13.0 M 65.0 98.0
12 JOE 13.0 M 63.0 105.0
13 MICHAEL 13.0 M 58.0 95.0
14 DAVID 13.0 M 59.0 79.0
15 JUDY 14.0 F 61.0 81.0
16 ELIZABETH 14.0 F 62.0 91.0
17 LESLIE 14.0 F 65.0 142.0
18 CAROL 14.0 F 63.0 84.0
19 PATTY 14.0 F 62.0 85.0
20 FREDERICK 14.0 M 63.0 93.0
21 ALFRED 14.0 M 64.0 99.0
22 HENRY 14.0 M 65.0 119.0
23 LEWIS 14.0 M 64.0 92.0
24 EDWARD 14.0 M 68.0 112.0
25 CHRIS 14.0 M 64.0 99.0
26 JEFFREY 14.0 M 69.0 113.0
27 MARY 15.0 F 62.0 92.0
28 AMY 15.0 F 64.0 112.0
29 ROBERT 15.0 M 67.0 128.0
30 WILLIAM 15.0 M 65.0 111.0
31 CLAY 15.0 M 66.0 105.0
32 MARK 15.0 M 62.0 104.0
33 DANNY 15.0 M 66.0 106.0
34 MARTHA 16.0 F 65.0 112.0
35 MARION 16.0 F 60.0 115.0
36 PHILLIP 16.0 M 68.0 128.0
37 LINDA 17.0 F 62.0 116.0
38 KIRK 17.0 M 68.0 134.0
39 LAWRENCE 17.0 M 70.0 172.0
//:*/
Names Default to Here(1);
Current Data Table() << Get Column Names;
/*:
{name, age, sex, height, weight}
Weitere Beispiele folgen in zukünftigen Blogbeiträgen.
Nachdem diese Funktion nun auf der diesjährigen Benutzerkonferenz vorgeführt wurde, kann ich sie mitteilen.
JMP 19 unterstützt das Portable Dataframe Protocol, das die Konvertierung von und zu Pandas oder anderen kompatiblen Dataframes in einer einzigen Codezeile ermöglicht. Bitte beachten Sie den Skriptindex in 19 EA oder 19, sobald Sie ihn erhalten.
import pandas as pd
import numpy as np
pandas_df = pd.DataFrame(
{
"A": 1.0,
"B": pd.Timestamp("20130102"),
"C": pd.Series(1, index=list(range(4)), dtype="float32"),
"D": np.array([3] * 4, dtype="int32"),
"E": pd.Categorical(["test", "train", "test", "train"]),
"F": "foo",
}
)
print(pandas_df)
dt = jmp.from_dataframe(pandas_df)
print(dt)
Das jmp-Paket verfügt nun über eine from_dataframe( df )-Methode, die aus dem Dataframe im Speicher eine JMP-Datentabelle erstellt. Pandas unterstützt außerdem das Portable Data Frame-Protokoll. Dank der Implementierung der Schnittstelle kann Pandas einen Dataframe direkt aus einem jmp.DataTable-Objekt erstellen.
import jmp
import pandas as pd
dt = jmp.open(jmp.SAMPLE_DATA + "Big Class.jmp")
pandas_df = (pd.api.interchange.from_dataframe(dt))
print(pandas_df)
Weitere Pakete, die das Data Frame-Protokoll unterstützen, sind Polars und Ibis. Beispiele finden Sie auch hier im Skriptindex unter 19.
Ich habe kürzlich JMP 19.1.1 installiert und die Methode `jmp.from_dataframe()`, wie oben von @Paul_Nelson beschrieben, anhand seines Beispielcodes ausprobiert. Dabei stellte ich fest, dass weiterhin die temporäre CSV-Datei verwendet wurde und folgende Meldungen im Protokoll erschienen:
:16: Pandas4Warning: The Dataframe Interchange Protocol is deprecated.
For dataframe-agnostic code, you may want to look into:
- Arrow PyCapsule Interface: https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html
- Narwhals: https://github.com/narwhals-dev/narwhals
ValueError: ArrowCTypes for string columns must be UTF-8 Strings or UTF-8 Large Strings.
The above exception was the direct cause of the following exception:
SystemError:
returned a result with an exception set
Weitere Recherchen ergaben, dass das von JMP verwendete Dataframe Interchange Protocol in pandas 3.0 anscheinend als veraltet markiert wurde.
Ich hatte pandas 3.0.2 zusammen mit meiner JMP-Instanz von Python installiert. Nach dem Downgrade auf pandas 2.2.3 verschwanden die Meldungen im Log und die JMP-Datentabelle wurde generiert, ohne dass eine Zwischen-CSV-Datei erstellt wurde.
Pandas 3.0 hat einige Probleme verursacht. Die Behandlung von String-Spalten hat sich in Pandas von Version 2.x zu 3.x geändert. Dies führte zu einem Fehler in unserer Verarbeitung von String-Spalten, was wiederum den Rückgriff auf CSV in Pandas 3.0.x und JMP 19.x erforderlich machte. Wir haben das Problem in JMP 20 behoben und prüfen derzeit, ob eine Anpassung in Version 19.2 möglich ist. Umfangreiche Codeänderungen waren notwendig.