JMP 中的 Python:数据框列到数据表列的最佳实践及对计算速度的影响背景
* 在 JMP19 脚本环境下使用 Python。
* 从现有的 JMP 数据表 (dt) 中,使用 pandas 创建数据框 (df)。
* 运行一个需要数据框输入并返回一个新的数据框结果的函数。我们称之为 df_results。
* 将结果传输回 JMP 数据表列。
这段代码可以运行,但有两点让我怀疑我的操作有问题。首先,本地日志输出中出现了警告。其次,第一次处理大量数据时耗时很长。我的代码计时器显示处理 10 万行数据需要 25 秒。如果重新运行,第二次只需要 3 秒。我设置了计时器,以便记录每个步骤的详细信息,从而找出是否是哪一行代码耗时过长。
我质疑的就是这一行代码。第一次填充初始值为常量(或空值——两种情况都试过)的列时,处理 10 万行数据就花了 25 秒。之后每次重试速度都快了近 10 倍。
dt[ColumnName] = df_results[ColumnName]
这是嵌入式日志中的警告消息
FutureWarning:Series.__getitem__ 将键视为位置的做法已弃用。在未来的版本中,整数键将始终被视为标签(与 DataFrame 的行为一致)。要按位置访问值,请使用 `ser.iloc[pos]`
此外,我对来自两个不同数据框的总共 4 列进行了相同的操作。它们都表现出相同的行为(和警告):首次运行速度很慢,之后每次重复运行速度都很快。第 1 列、第 2 列、第 3 列和第 4 列之间的差异可以忽略不计。
这只是一个包含 130 万行的大表的一个子集。所以,如果我第一次尝试时无法找到提高速度的方法,那么 25 秒 x 4 列就会变成 250 多秒 x 4 列。
谢谢
自动化和脚本编写回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响在 JMP 18 中,需要逐列地从数据帧构建数据表。JMP 19 支持可移植数据帧协议,因此只需一行代码即可将数据表转换为数据帧,同样只需一行代码即可将数据帧转换为数据表。
另请注意,首次在 JMP 中运行 Python 脚本时,需要初始化 Python 本身。首次导入包时,需要加载该包,可能包括共享库或 .py 文件的字节码编译。
此警告来自 pandas 包。要消除此警告,您需要执行其建议的 `ser.iloc[pos]`。JMP 数据表 dt 可以使用从 0 开始的列索引或列名进行索引。使用索引速度更快,因为它避免了字符串比较。
帮助 -> 脚本索引“Python”类别,jmp.from_dataframe。“JMP 到 Pandas”示例。
import jmp
import jmputils
try:
if not jmputils.is_installed('pandas'):
jmputils.jpip('install', 'pandas', echo=False)
except Exception as e:
print(f'Install failed with exception: {e}')
import pandas as pd
dt = jmp.open(jmp.SAMPLE_DATA + "Big Class.jmp")
pandas_df = (pd.api.interchange.from_dataframe(dt))
print(pandas_df)
...
# new dt_result data table from dataframe df_result
dt_result = jmp.from_dataframe( df_result )
# or continue to do the loop on columns if you need to update the original data table from the resulting dataframe.
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响那么,用 Python 代码向 JMP 文件中的单个列填充新值的最快方法是什么?
例如,对于一个包含 10 万行数据的数据表?
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响谢谢 Paul,我会使用 jmp.from_dataframe 函数返回到数据表,以避免出现错误。
我问题的另一部分是关于为什么速度差异如此之大,为了回答这个问题,我必须确保能够可靠地重现计算响应缓慢的情况。起初我做不到。所有运行,甚至包括打开 JMP 并加载 Python 后的第一次运行,速度都非常快。后来我意识到可能存在一个关键区别。如果我手动添加一个新列(尚未将其写入代码),然后运行代码,则只有在更新该新列时才会出现速度缓慢的情况,而且这种情况仅发生在第一次更新时。
作为测试,我更新了 4 列,其中 3 列是原有列,第 4 列是新创建的。更新第 1 列的 10 万行数据耗时 1 秒,更新第 2 列又耗时 1 秒,更新第 3 列又耗时 1 秒,而更新第 4 列则耗时 19 秒。
我创建了另一个表格,并添加了一个新的第四列。如果我保存并关闭包含新列的数据表,然后再重新打开,最后运行代码……新的第四列会在大约 1 秒内更新,速度与第 1、2 和 3 列一样快。关键是,如果我只保存而不关闭并重新打开包含新第四列的数据表,然后运行代码,更新第四列仍然需要大约 19 秒。
这说明速度问题与 JMP 的某些内存管理特性有关。
我不知道这个问题是否能/必须解决,也许只有在不使用 `from_dataframe` 步骤时才会出现。这确实是一个非常奇怪的现象,我很庆幸能够重现它。现在我可以尝试改进后的代码,看看问题是否仍然存在。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响我的一位同事指出,JSL 中有“开始数据更新”和“结束数据更新”,所以……
jmp.run_jsl( 'dt << Begin Data Update;' )
# your python code filling new column
...
jmp.run_jsl( 'dt << End Data Update;' )
我们很可能需要用 Python 来实现这个功能。可能会使用 with 语句块来暂停表格的 UI 更新,以便在填充/更新大量行时使用。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响它会使用包装在 jsl Begin Data Update 中的 dt[index] 来设置整列。
jmp.run_jsl( 'dt << Begin Data Update;' )
dt[ index ] = list_like_object # numpy array, list, dataframe column,...
jmp.run_jsl( 'dt << End Data Update;' )
或者,您可以先将数据表设置为不可见,直到填满表格后再将其显示出来。
import jmp
dt = jmp.DataTable('Powered By Python', 40, visibility='Invisible')
...
# fill table
...
jmp.globals['jdt'] = dt # set jdt in JSL to reference dt
jmp.run_jsl('::jdt << Show Window(1);')
可选的可见性参数可能需要 JMP 19 或更高版本。jmp.globals[ ] 是 JMP 19 的一个特性。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响`jmp.run_jsl('dt << Begin Data Update;')` 假定 `dt` 是 JSL 中表的 JSL 变量,Python 端也是如此。例如,如果 JSL 中执行了 Python 的 `Send(dt)` 函数,则会出现这种情况;但如果表是由 Python 创建的,则不一定会出现同样的情况。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响非常感谢您的解释。
所以,代码如下:
dt[ index ] = list_like_object
Python中是逐步填充列的吗?这导致了速度问题?
它看起来很像
dt[ 0, columnname] = [values ...]
在JSL中,它可以一步填充列,无需用`dt << Begin/end Data Update`包裹。
知道了。请问哪里可以找到更多详细信息?
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响我不知道有多少用户了解它的重要性。
dt << Begin Data Update;
这是为了编写优秀的代码。但我相信很多用户并不知道这一点。
@bswedlove提出的相关愿望是针对每一行:
对于每一行(数据更新=1, ...)
将解决方案制度化是个好主意——这样用户就不会再遇到问题了(解决方案一直都在那里)。
适用于 JSL 和 Python。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响JMP 数据表 dt 可以通过从 0 开始的列索引或列名进行索引。使用索引速度更快,因为它避免了字符串比较。
我尝试使用索引重写代码,但似乎找不到与数据框中根据名称确定数据表列索引等效的语法。
dfColumnIndex = df.get_loc['数据框列名称']
dtColumnIndex = ???
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响@jthi ,请问“添加一些代码来修复一个并不存在的问题”这种做法叫什么?
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响如果 df 是由 dt 创建的,那么通常假设它们具有相同的索引。如果您向数据框中添加了列,或者两者并非源自同一数据框,那么使用 `dt['col_name'] =` 可能是您唯一的选择。与使用索引相比,这种方法的开销应该不会太大,但它本质上是字符串比较,而不是直接使用数值索引。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响这一切都发生在 Python 的 Protocol Buffers 的神奇之处,数据会在内存中被映射到支持的内存块之间。虽然可能不需要使用 `Begin | End Data Update` 进行分隔,但如果无法一次性映射整列数据,它会回退到遍历每个值的方式,因此使用 `Data Update` 语句进行分隔是一个很好的安全措施。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响我进行了大量实验,尝试了不同的方法。有些方法会抛出警告,有些则不会。最简单的方法似乎是从数据框 (df) 创建一个数据转换表 (dt),然后使用该数据转换表来传输数据。这种方法从未报错。
然而,关于速度问题……唯一产生影响的因素是,我尝试向新创建的列写入值,还是尝试向数据表打开(或创建)时已存在的列写入值。新创建的列更新速度极慢,而已存在的列更新速度则非常快。
上次测试结果基于 10 万行数据。运行此代码前,已存在 4 个参数,并新增了 5 列。总共更新了 9 列。
#这些“xfers”都使用了 dt['colname'] = dt_results['colname'] 语法。在用 DF 创建 dt_results 之后,没有涉及任何 DataFrame。
创建步骤 df_results 耗时:0.3434 秒
步骤参数 1 传输时间:0.1795 秒
步骤参数 2 传输时间:0.1758 秒
步骤 df_results 创建时间:0.0231 秒 #这是不同 df 函数返回的新结果集。
步骤参数 3 传输时间:0.1838 秒
步骤参数 4 传输时间:0.1890 秒
#此处的代码为:dt['NewCol1Test'] = df_results[stringnameofcolumn]
FutureWarning:Series.__getitem__ 将键视为位置的做法已弃用。在未来的版本中,整数键将始终被视为标签(与 DataFrame 的行为一致)。要按位置访问值,请使用 `ser.iloc[pos]`
将数据框 (df) 转换为数据表 (dt) 并命名,耗时:15.8251 秒
#此处代码为:dt['NewCol2Test'] = df_results.iloc[:,dfColumnIndex]
FutureWarning:Series.__getitem__ 将键视为位置的做法已弃用。在未来的版本中,整数键将始终被视为标签(与 DataFrame 的行为一致)。要按位置访问值,请使用 `ser.iloc[pos]`
我不知道为什么在使用iloc时仍然会抛出那个警告。
使用 iloc 传输将 df 转换为 dt 耗时:16.0874 秒
#此处代码为:dt['NewCol3Test'] = dt_results[dfColumnIndex]
dt 索引到 dt 名称的传输耗时:14.5659 秒
#此处代码为:dt[NewColumnIndex] = dt_results[dfColumnIndex]
dt 索引到 dt 索引的传输时间:14.4699 秒
此处的代码是:
jmp.run_jsl('dt << 开始数据更新;')
dt['NewCol5Test'] = dt_results[dfColumnIndex]
jmp.run_jsl('dt << 结束数据更新;')
#警告/错误结果:
"名称未解析:在访问或评估“dt”时出现 dt,dt/*###*/
第 1 行
名称未解析:访问或评估“dt”时出现“dt”错误,dt/*###*/
第 1 行”
#实际上,这条打印信息与第二个“第 1 行”错误消息在同一行。
dt 到 dt w 索引封装在 begin/end 更新传输中,耗时:14.3627 秒
#10万行数据运行时间的最终总结。
总执行时间:77.0904 秒
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响再次运行此代码 (**) 之前,请保存您的数据 (*) - 或启用开始/结束数据更新!
import jmp
import numpy as np
dt = jmp.open(jmp.SAMPLE_DATA + "Wafer Stacked.jmp")
jmp.globals['jdt'] = dt
jmp.run_jsl('Graph Builder( Variables( Y( :defects ) ), Elements( Bar( Y, Label( "Label by Value" ) ) ))');
# jmp.run_jsl('jdt << Begin Data Update;')
dt[ 5 ] = [np.random.randint(1000)]*177875
# jmp.run_jsl('jdt << End Data Update;')
(*)开玩笑啦,你不会丢失数据的 - 喝杯咖啡,等 JMP 完成就好。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响
@hogi写道:
第二次(**)
(**)TS - 00251136
@Dahlia_Watkins注意到,有时需要执行两次代码才会遇到速度问题。
看起来像是时机上的“问题”。
或许:当你第一次运行代码时,要等到 Python 完成逐行数据更新后才会创建图表。
编辑:
实际上,图表并非必须作为交互元素才能使 Python 数据更新变慢。
以下帖子中提供了更多不带交互元素的示例。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响我已将此主题添加到“注意:Jmp 函数可能出现意外行为”部分。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响针对最初的问题:“如果存在新列,则数据访问速度会变慢”
以下是一个示例:
import jmp
dt = jmp.DataTable('dt_new', 50000)
col = dt.new_column("newCol")
# --------- cut here to make it slow - why? -------------
dt[ 0 ] = [1]*50000
分成 2 个步骤,JMP 需要几秒钟才能更新 50,000 个条目。
令人费解:
如果一次性执行两个代码块,JMP 不会出现问题。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响除了@Paul_Nelson帖子中提到的解决方法之外,我认为还有一些其他的方法,例如:
- 使用 JSL 创建列/数据表
- 使用 JSL 向列中填充虚拟值
- 使用 JSL 保存数据表,关闭并重新打开它。
它们乍一看都很不错——但这具有误导性。
速度快的原因是相同的值被重复写入列中,而 JMP 没有触发“数据更新警报”:
例如
jmp.globals['jdt'] = dt
jmp.run_jsl('''
nc = n cols(jdt);
Column(jdt,nc) << set each value(1)
''');
nc = dt.ncols
data = [1]*50000
dt[ nc - 1 ] =data
用Python也能实现同样的奇迹:
# 1st: slow
nc = dt.ncols
dt[ nc - 1 ] = range(50000)
# fast ! (same values!)
dt[ nc - 1 ] = range(50000)
# new values: slow again
dt[ nc - 1 ] = [5]*50000
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响您可以设置一个触发器,以便在 JMP 认为数据更新时收到通知。
import jmp
dt = jmp.open(jmp.SAMPLE_DATA + "Big Class.jmp")
jmp.run_jsl('''
New Column( "trigger",
Formula(
If( Row() == 1,
Try( Caption( Char( ::i=::i+1 ) ), ::i = 1 )
);
:age;i
)
)
''')
dt[ 1 ] = range(40)
dt[ 1 ] = range(40) # no data update !
dt[ 1 ] = [2]*40
# for loop without begin/end data update -> finished before JMP notices the data update
jmp.run_jsl('''
for each row(:age = row());
wait(5);
''')
# for loop without begin/end data update - and some delay to give JMP the time to notice:
jmp.run_jsl('''
for each row(:age = 2*row();wait(0))
''')
所以,当原始值和新值完全相同时,确实不存在“数据更新”。
是为了解决无限循环问题而实施的吗?太好了!
有趣的,
import jmp
dt = jmp.open(jmp.SAMPLE_DATA + "Wafer Stacked.jmp")
jmp.run_jsl('''
delete symbols(::i)
New Column( "trigger",
Formula(
If( Row() == 1,
Try( Caption( Char( ::i=::i+1 ) ), ::i = 1 )
);
:Y_Die;i
)
)
''')
nr = dt.nrows
dt[ 4 ] = range(nr)
耗时超过 1 分钟——我数到 2。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响
@Paul_Nelson wrote:
...
Most likely we will want to do support this from Python. Probably using a with block to suspend the UI updates of the table when filling / updating large number of rows.
好主意!
即使 Python 在“with”代码块中抛出错误,也请务必重新启用交互功能。
另一种选择是:
请在数据网格中添加一个指示器,以显示交互功能何时因某些原因而被禁用。
开始数据更新,但没有结束数据更新。
指示器:此数据表的交互功能已禁用
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响前几篇文章的研究结果——精简如下:
import jmp
# open data table and wait for the "Interactivity Manager" to get active
jmp.run_jsl('''
dt = new table("test", add rows(100000), new column ("col"));
Python send(dt);
wait(0) // disable the delay to make it fast
'''
);
nr=dt.nrows
# Python data update: slow!
dt[ 0 ] = range(nr)
# --- run step by step: -----
# same values: fast
dt[ 0 ] = range(nr)
#new values: slow
dt[ 0 ] = [1]*nr
通过 Python 更新 JSL 列的速度非常慢。
例外情况:
- 创建/打开数据表后立即授予写入权限(在某种“交互管理器”激活之前)
- 没有任何更新(数值没有变化)
以及@Paul_Nelson帖子中提到的变通方法:
- 私有数据表
- Python 数据更新被JSL 开始/结束数据更新()包裹

借助 JSL,即可实现快速的 Python 数据访问 
我想这些问题应该足够严重,可以在下一个“.x”版本中得到解决……
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响实际上,第二种情况需要将表格设为私有,即 visibility='Private'。“不可见”的表格即使隐藏了,仍然会显示图形用户界面 (GUI),因此每次编辑单元格时数据表格都会更新 GUI,导致 GUI 界面更新,从而造成卡顿。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响是的,这非常重要。我们确实正在寻找解决方案。我们希望 Python 的性能能够达到用户愿意直接使用 Python 运行程序,而不是为了提升性能而使用一些不正当的技巧方法。
我很惊讶这个问题没有在 JMP 18 或 JMP 19 EA 测试中出现。jmp.DataTable 对象的核心是 JSL 数据表。Python 对象只是对底层 JMP 数据表的引用,我们调用的是 JMP 数据表的内部方法,这就是为什么 `run_jsl('dt << Begin Data Update')` 会产生影响。`jmp.run_jsl(''' code '''')` 的全部意义在于允许从 Python 调用 JSL 来处理我们尚未实现的功能,或者像本例中一样,纠正列更新性能方面的疏忽。
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响很高兴听到问题已解决。
为什么之前没有发现?
我猜想很多用户在将数据传输到 JMP 之前,会使用 Python 进行数据导入/数据整理——在 JMP 中,他们不会再通过 Python 直接操作数据。如果他们使用Python 的 `get(dt)`(JSL)或`from_dataframe()`(Python)函数,就不会遇到这个问题。
注意:逐列传输,如JMP 18 中的 Python 集成入门指南中所述。
这取决于数据表的大小。如果表格足够小,新列会在“交互管理器”激活之前添加。
当我们发现问题时,我们可以使用 JMP19EA,并且很高兴地发现我们可以通过from_dataframe()来加快表格生成速度,而且代码也更加简单。
另一方面:当使用 Python更新现有 JMP 数据表中的单个列时,就会出现这个问题,例如在JMPyFacade:连接 JMP 和 Python 以实现无缝互动分析中。作为 JSL 专家, @jthi肯定毫不犹豫地添加了必要的私有表合并数据变通方案;)
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响今天脚本编写者俱乐部分享的信息:
速度问题
dt[ index ] = list_like_object
将在 JMP 19.0.3 版本中修复 
回复:JMP 中的 Python:数据框列到数据表列的最佳实践及计算速度影响现已推出:
