JMP 18 offre nuove potenti funzionalità nell'integrazione Python. Include una versione incorporata di Python 3.11, supporto per l'installazione del pacchetto, editor di script compatibile con Python in grado di eseguire script Python puri, un pacchetto jmp di importazione che fornisce accesso Python alle funzionalità JMP, segnalazione degli errori migliorata e supporto dell'indice di scripting che documenta e fornisce esempi per la nuova funzionalità in una categoria "Python".

Il focus qui è specificamente l'oggetto jmp.DataTable e le modifiche apportate da JMP 18 rispetto a Python Send ( dt ) e Python Get ( df ) . Dove "dt" è una tabella dati e "df" è un pandas.DataFrame.
JMP 14-17 forniva la possibilità di trasferire una copia di una tabella di dati JMP nell'ambiente Python utilizzando il comando JSL Python Send( dt ); . Il modo in cui funzionava dietro le quinte prevedeva che JMP scrivesse la tabella dati in un file CSV temporaneo nel file system, quindi invocasse i panda per leggere il file CSV dal disco in un DataFrame. Funziona bene per file di piccole dimensioni ma non è adatto per dati più grandi. Innanzitutto c'è il problema di dover utilizzare il file system per il trasferimento dei dati. In secondo luogo, passare dal binario al testo e poi di nuovo al binario può comportare una perdita di precisione. Successivamente, il file CSV è solitamente notevolmente più grande dei dati binari originali. Infine, hai una copia dei dati in Python, non un modo per manipolare la tabella originale. Riportare i dati elaborati da Python in JMP utilizzando Python Get (df); fa esattamente lo stesso processo al contrario. Python Ottieni(df); è stato lasciato intatto per motivi di compatibilità.
Nuovo nella versione 18, Python Get (dt); recupera il riferimento della tabella dati a JSL della tabella effettiva. Questa è un'operazione di copia zero. Per necessità Python Send( dt ); non crea più un pandas.DataFrame. In Python ottieni un oggetto jmp.DataTable che è un riferimento live a Tabella dati JMP. Anche questa è un'operazione di copia zero. Consente l'accesso diretto da Python per accedere, modificare e modificare la tabella dati JMP. È possibile creare un oggetto jmp.DataTable da Python, creando nel frattempo una nuova tabella JMP.
Di seguito è riportato uno script JSL che utilizza le funzionalità JMP 18.0 per prendere una tabella di dati JMP, aggiungere una colonna che è il rapporto tra due colonne da Python. Eseguendo questa operazione dal vivo sulla tabella dati corrente.
// 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]
]\");

Il risultato è l'output del registro:
/*:
Nomi di classe grande: 40 ncols: 5 nome età sesso altezza peso KATIE 0
Ora mostriamo come lavorare direttamente da Python. Di seguito è riportato uno script Python che apre "Big Class.jmp", crea un frame di dati panda in memoria dall'oggetto jmp.DataTable, chiude "Big Class", quindi crea una nuova tabella di dati JMP da DataFrame e infine in JSL stampa i nomi delle colonne della tabella dati corrente. La tabella corrente è quella appena creata in Python.

# 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;
''')
Avere l'output del registro
/*:
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}
Altri esempi da seguire nei futuri post del blog.
Ora che questa funzionalità è stata dimostrata alla User Conference di quest'anno, posso condividerla.
JMP 19 supporta il Portable Dataframe Protocol, che ci consente di convertire da e verso un pandas o qualsiasi altro dataframe conforme in una singola riga di codice. Consulta l'indice di scripting in 19 EA o 19 quando lo ottieni.
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)
Il pacchetto jmp ora ha un metodo from_dataframe( df ) che creerà una tabella dati JMP dal dataframe in memoria. Pandas supporta anche il protocollo Portable Data Frame. Poiché abbiamo implementato l'interfaccia, pandas può creare un dataframe direttamente da un oggetto jmp.DataTable.
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)
Altri pacchetti che supportano il protocollo Data frame includono Polars e Ibis. Di nuovo, vedere l'indice di scripting in 19 per esempi.
Ho installato di recente JMP 19.1.1 e ho provato il metodo jmp.from_dataframe() come descritto sopra da @Paul_Nelson , utilizzando il suo codice di esempio. Ho scoperto che utilizzava ancora il file CSV temporaneo intermedio e ho ricevuto i seguenti messaggi nel log:
: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
Ulteriori indagini hanno rivelato che il Dataframe Interchange Protocol, utilizzato da JMP, sembra essere stato deprecato in pandas 3.0.
Avevo installato pandas 3.0.2 con la mia istanza di Python per JMP. Sono tornato alla versione 2.2.3 di pandas e i messaggi nel log sono scomparsi e la tabella dati di JMP è stata generata senza che venisse creato un file CSV intermedio.
Pandas 3.0 ha causato alcuni problemi. La gestione delle colonne di tipo stringa è cambiata tra la versione 2.x e la 3.x di Pandas. Questo ha generato un errore nell'elaborazione delle colonne di tipo stringa, che ha poi portato al ripiegamento sul formato CSV con Pandas 3.0.x e JMP 19.x. Abbiamo risolto il problema in JMP 20 e stiamo valutando se sia possibile includerlo nella versione 19.2. È stato necessario modificare una quantità considerevole di codice.