Mathematica 导出科学计数法数据

Mathematica 导出数据总是遇到很多坑,总结一下。

个人日常喜欢导出 .csv 格式的文件,即数值用逗号分隔。

常用方法: Export 函数

在 mathemtica 中,最常用的导出函数莫过于 Export。试举一例

先来创建一组数据

1
data = Table[{a, 10^-7 a}, {a, 1., 10, 1}]

输出

1
2
3
{{1., 1.*10^-7}, {2., 2.*10^-7}, {3., 3.*10^-7}, {4., 4.*10^-7}, {5., 
5.*10^-7}, {6., 6.*10^-7}, {7., 7.*10^-7}, {8., 8.*10^-7}, {9.,
9.*10^-7}, {10., 1.*10^-6}}

使用 Export 函数导出

1
2
SetDirectory[NotebookDirectory[]];
Export["test.csv", data];

test.csv文件内容

1
2
3
4
5
6
7
8
9
10
1.,1.e-7
2.,2.e-7
3.,3.e-7
4.,4.e-7
5.,5.e-7
6.,6.e-7
7.,7.e-7
8.,8.e-7
9.,9.e-7
10.,1.e-6

到目前为止都没什么问题,Export 函数能够自动转换 8.e-7 这类科学计数法表示的数据。

挖坑

使用 Export 可以直接导出一个变量,那么在必须要迭代的程序中(并且不知道迭代次数),如何把结果累积在一个变量中呢?跟很多程序语言类似,Append 或者 AppendTo 可以追加内容到一个列表中。这个函数众所周知的问题,就是在数据量太大的时候异常卡慢。我们可以比较一下。

定义一个测试函数,输出不同迭代次数下,使用 AppendTo 追加,以及最终导出数据所需时间

1
2
3
4
5
testfunc[num_] := 
Block[{data = {}, i, time}, i = 1;
AbsoluteTiming[
Do[list = {i, 10.^-7 i}; AppendTo[data, list]; i++, num];
Export["test.csv", data];][[1]]]

观察一下不同迭代次数下的需要耗费的时间

1
2
3
SetDirectory[NotebookDirectory[]];
data = Table[{n, testfunc[n]}, {n, 10000, 100000, 10000}];
ListLinePlot[data]

从计算时间上可以看到,耗费时间与迭代次数并非线性增加,因此再操作大型数组的术后 AppendTo 就退下吧。

看到这里好像有点跑题?其实这里主要是想说明:通过 AppendTo 追加数组,最终再使用 Export 自动输出科学计数法的数值的方案不可行。另外,PutAppend 函数也类似,不适用大型数组叠加输出。

填坑

这时候只能想原始一点的办法了,想来 Mathematica 应该会提供类似 c++ 文件流的方式,查找一下帮助文档里面的方案:

1
2
3
4
str = OpenWrite["test.csv"];
test = 1.2 10^-7;
WriteString[str, test];
Close[str];

但是这时候导出的结果只能用混乱来形容。。。

1
2
      -7
1.2 10

得想办法如何转换成想要的数值格式,在帮助文档 NumberFormat 函数中给出了一个例子,经过(交流群的混乱讨论),加工一下

1
2
test = 1.2 10^-7;
ScientificForm[test, 10, NumberFormat -> (Row[{#1, "e", #3}] &)]

但是注意 WriteString 接收的是字符串格式,加个 ToString 转换一下即可,另外在 1-10 范围内的应该不需要科学计数法表达,再混乱地整理一下,

1
2
3
4
Num2Str[num_] := 
If[1 > =num || num > 10,
ScientificForm[num, 6, NumberFormat -> (Row[{#1, "e", #3}] &)] //
ToString // StringReplace[#, ".e" -> ".0e"] &, ToString@num];

废话有点多了。。。最终再来测试下速度

1
2
3
4
5
6
7
8
testfunc[num_] := 
Block[{data = {}, i, time, str},
AbsoluteTiming[str = OpenWrite["test.csv"]; i = 1;
Do[list = Num2Str /@ {i, 10.^-7 i};
WriteString[str, list[[1]] <> "," <> list[[2]] <> "\n"]; i++,
num];
Close[str];][[1]]
]
1
2
data = Table[{n, testfunc[n]}, {n, 10000, 100000, 10000}];
ListLinePlot[data]

时间明显减少很多,并且线性更强。对比一下 AppendTo 的结果,感动。

最终敲定,在 Mathematica 循环输出科学计数法数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(*数值格式转换*)
Num2Str[num_] :=
If[1 > =num || num > 10,
ScientificForm[num, 6, NumberFormat -> (Row[{#1, "e", #3}] &)] //
ToString // StringReplace[#, ".e" -> ".0e"] &, ToString@num];

(*文件流写入时间测试函数*)
testfunc[num_] :=
Block[{data = {}, i, time, str},
AbsoluteTiming[str = OpenWrite["test.csv"]; i = 1;
Do[list = Num2Str /@ {i, 10.^-7 i};
WriteString[str, list[[1]] <> "," <> list[[2]] <> "\n"]; i++,
num];
Close[str];][[1]]
]

(*性能测试*)
data = Table[{n, testfunc[n]}, {n, 10000, 100000, 10000}];
ListLinePlot[data]

群友提供的另外一种转换方式 ToString[1.23*^45, CForm]


懒得补图了,就酱🤣。