向量化自定义解析函数会产生 ValueError
Vectorizing a custom parsing function gives a ValueError
我写了一个自定义数字解析函数。基本上,我想将 Google Play 商店(5.6M、3M、112K)中给出的应用程序大小信息转换为标准浮点数。
要将此函数应用于我的数据框中的一列数据,我想使用 numpy.vectorize 对其进行矢量化。但是,当我对其进行测试时,出现错误。
这是函数:
import numpy as np
import re
def parse_numbers(x, homo = False):
if homo == False:
if bool(re.match("^[0-9.]+[Mm]{1}$", x)):
new_number = float(re.sub("[^0-9.]", "", x))
return new_number * 1000000
elif bool(re.match("^[0-9.]+[Kk]{1}$", x)):
new_number = float(re.sub("[^0-9.]", "", x))
return new_number * 1000
else:
return(x)
elif homo == True:
if bool(re.match("^[0-9.]+[MmKk]{1}$", x)):
return "parsed_number"
else:
return(x)
else:
return "invalid setting for homo attribute"
如您所见,如果它收到任何无法解析为数字的输入,它 return 就是原始输入。
当我手动测试时,它工作正常:parse_numbers("3.1M")
returns 3100000
和 parse_numbers("not a number")
returns not a number
.
现在我尝试对函数进行矢量化,我这样测试它:
vparse_numbers = np.vectorize(parse_numbers)
vparse_numbers(["3.1M", "2k", "not a number"])
我收到以下错误:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_27076/2263610390.py in <module>
21
22 vparse_numbers = np.vectorize(parse_numbers)
---> 23 vparse_numbers(["3.0M", "2k", "not a number"])
c:\programdata\miniconda3\lib\site-packages\numpy\lib\function_base.py in __call__(self, *args, **kwargs)
2161 vargs.extend([kwargs[_n] for _n in names])
2162
-> 2163 return self._vectorize_call(func=func, args=vargs)
2164
2165 def _get_ufunc_and_otypes(self, func, args):
c:\programdata\miniconda3\lib\site-packages\numpy\lib\function_base.py in _vectorize_call(self, func, args)
2247
2248 if ufunc.nout == 1:
-> 2249 res = asanyarray(outputs, dtype=otypes[0])
2250 else:
2251 res = tuple([asanyarray(x, dtype=t)
ValueError: could not convert string to float: 'not a number'
当我仅使用可解析的数字对其进行测试时:vparse_numbers(["3.1M", "2k", "not a number"])
它会向我 return 列出正确的数字。
我在这里错过了什么?我没有正确使用 numpy.vectorize 函数吗?
np.vectorize
创建不能混合不同类型数据的 numpy.array
- string
和 float
- 它会尝试将所有数据转换为 float
.您必须 return np.NaN
而不是所有字符串。
import numpy as np
import re
import pandas as pd
def parse_numbers(text, homo=False):
if homo == False:
if bool(re.match("^[0-9.]+[Mm]{1}$", text)):
new_number = float(re.sub("[^0-9.]", "", text))
return new_number * 1_000_000
elif bool(re.match("^[0-9.]+[Kk]{1}$", text)):
new_number = float(re.sub("[^0-9.]", "", text))
return new_number * 1_000
else:
return np.NaN
elif homo == True:
if bool(re.match("^[0-9.]+[MmKk]{1}$", text)):
return "parsed_number"
else:
return np.NaN
else:
raise Exception(f"invalid setting for homo attribute: {homo}")
vparse_numbers = np.vectorize(parse_numbers)
vparse_numbers(["3.1M", "2k", "not a number"])
但是如果你想使用 DataFrame
那么你应该使用 .apply()
它可以 return 不同类型的数据。
import numpy as np
import re
import pandas as pd
def parse_numbers(text, homo=False):
if homo == False:
if bool(re.match("^[0-9.]+[Mm]{1}$", text)):
new_number = float(re.sub("[^0-9.]", "", text))
return new_number * 1_000_000
elif bool(re.match("^[0-9.]+[Kk]{1}$", text)):
new_number = float(re.sub("[^0-9.]", "", text))
return new_number * 1_000
else:
return text # np.NaN
elif homo == True:
if bool(re.match("^[0-9.]+[MmKk]{1}$", text)):
return "parsed_number"
else:
return text # np.NaN
else:
raise Exception(f"invalid setting for homo attribute: {homo}")
df = pd.DataFrame({"test": ["3.1M", "2k", "not a number"]})
df['result'] = df['test'].apply(parse_numbers)
print(df)
结果:
test result
0 3.1M 3100000.0
1 2k 2000.0
2 not a number not a number
编辑:
我会写parse_numbers
有点不同
- 只有一行
re
。
- 使用
{,1}
将字符串也转换为数字 "123"
- 最后转换为
integer
- 使用不带
s
的名称 parse_number
因为它只解析一个数字。
import re
import pandas as pd
def parse_number(text, homo=False):
if not isinstance(homo, bool):
raise Exception(f"invalid setting for homo attribute: {homo}")
results = re.findall("^([0-9.]+)([MmKk]{,1})$", text)
#print(results)
if results:
if homo:
return "parsed_number"
else:
number, name = results[0]
new_number = float(number)
if name in ('M', 'm'):
new_number *= 1_000_000
elif name in ('K', 'k'):
new_number *= 1_000
return int(new_number)
else:
return text # np.NaN
df = pd.DataFrame({
"test": ["3.1M", "2k", "123", "not a number"]
})
df['result'] = df['test'].apply(parse_number)
df['homo'] = df['test'].apply(lambda x:parse_number(x, True))
print(df)
结果:
test result homo
0 3.1M 3100000 parsed_number
1 2k 2000 parsed_number
2 123 123 parsed_number
3 not a number not a number not a number
In [475]: alist = ["3.1M", "2k", "not a number"]
将您的函数直接应用于列表元素:
In [476]: [parse_numbers(s) for s in alist]
Out[476]: [3100000.0, 2000.0, 'not a number']
np.vectorize
如果您指定正确的 otypes 将起作用:
In [478]: fn = np.vectorize(parse_numbers, otypes=[object])
In [479]: fn(alist)
Out[479]: array([3100000.0, 2000.0, 'not a number'], dtype=object)
In [480]: timeit [parse_numbers(s) for s in alist]
12.9 µs ± 51.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [481]: timeit fn(alist)
24.1 µs ± 33.4 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
对于返回对象,frompyfunc
更快一些,没有一些 np.vectorize
开销:
In [482]: fn = np.frompyfunc(parse_numbers, 1,1)
In [483]: fn(alist)
Out[483]: array([3100000.0, 2000.0, 'not a number'], dtype=object)
In [484]: timeit fn(alist)
21.1 µs ± 68.5 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
我对其他 SO 的观察是 pandas
apply
慢得多。 pandas
在跟踪索引方面做了很多额外的工作。
对于基本上面向字符串和列表的任务,使用 numpy
几乎没有任何优势。
对于更大的列表,时间差异消失了:
In [485]: alist = alist*1000
In [486]: len(alist)
Out[486]: 3000
In [487]: timeit fn(alist)
12.5 ms ± 70.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [488]: timeit [parse_numbers(s) for s in alist]
12.3 ms ± 319 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [489]: fn = np.vectorize(parse_numbers, otypes=[object])
In [490]: timeit fn(alist)
12.1 ms ± 42.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
我写了一个自定义数字解析函数。基本上,我想将 Google Play 商店(5.6M、3M、112K)中给出的应用程序大小信息转换为标准浮点数。
要将此函数应用于我的数据框中的一列数据,我想使用 numpy.vectorize 对其进行矢量化。但是,当我对其进行测试时,出现错误。
这是函数:
import numpy as np
import re
def parse_numbers(x, homo = False):
if homo == False:
if bool(re.match("^[0-9.]+[Mm]{1}$", x)):
new_number = float(re.sub("[^0-9.]", "", x))
return new_number * 1000000
elif bool(re.match("^[0-9.]+[Kk]{1}$", x)):
new_number = float(re.sub("[^0-9.]", "", x))
return new_number * 1000
else:
return(x)
elif homo == True:
if bool(re.match("^[0-9.]+[MmKk]{1}$", x)):
return "parsed_number"
else:
return(x)
else:
return "invalid setting for homo attribute"
如您所见,如果它收到任何无法解析为数字的输入,它 return 就是原始输入。
当我手动测试时,它工作正常:parse_numbers("3.1M")
returns 3100000
和 parse_numbers("not a number")
returns not a number
.
现在我尝试对函数进行矢量化,我这样测试它:
vparse_numbers = np.vectorize(parse_numbers)
vparse_numbers(["3.1M", "2k", "not a number"])
我收到以下错误:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_27076/2263610390.py in <module>
21
22 vparse_numbers = np.vectorize(parse_numbers)
---> 23 vparse_numbers(["3.0M", "2k", "not a number"])
c:\programdata\miniconda3\lib\site-packages\numpy\lib\function_base.py in __call__(self, *args, **kwargs)
2161 vargs.extend([kwargs[_n] for _n in names])
2162
-> 2163 return self._vectorize_call(func=func, args=vargs)
2164
2165 def _get_ufunc_and_otypes(self, func, args):
c:\programdata\miniconda3\lib\site-packages\numpy\lib\function_base.py in _vectorize_call(self, func, args)
2247
2248 if ufunc.nout == 1:
-> 2249 res = asanyarray(outputs, dtype=otypes[0])
2250 else:
2251 res = tuple([asanyarray(x, dtype=t)
ValueError: could not convert string to float: 'not a number'
当我仅使用可解析的数字对其进行测试时:vparse_numbers(["3.1M", "2k", "not a number"])
它会向我 return 列出正确的数字。
我在这里错过了什么?我没有正确使用 numpy.vectorize 函数吗?
np.vectorize
创建不能混合不同类型数据的 numpy.array
- string
和 float
- 它会尝试将所有数据转换为 float
.您必须 return np.NaN
而不是所有字符串。
import numpy as np
import re
import pandas as pd
def parse_numbers(text, homo=False):
if homo == False:
if bool(re.match("^[0-9.]+[Mm]{1}$", text)):
new_number = float(re.sub("[^0-9.]", "", text))
return new_number * 1_000_000
elif bool(re.match("^[0-9.]+[Kk]{1}$", text)):
new_number = float(re.sub("[^0-9.]", "", text))
return new_number * 1_000
else:
return np.NaN
elif homo == True:
if bool(re.match("^[0-9.]+[MmKk]{1}$", text)):
return "parsed_number"
else:
return np.NaN
else:
raise Exception(f"invalid setting for homo attribute: {homo}")
vparse_numbers = np.vectorize(parse_numbers)
vparse_numbers(["3.1M", "2k", "not a number"])
但是如果你想使用 DataFrame
那么你应该使用 .apply()
它可以 return 不同类型的数据。
import numpy as np
import re
import pandas as pd
def parse_numbers(text, homo=False):
if homo == False:
if bool(re.match("^[0-9.]+[Mm]{1}$", text)):
new_number = float(re.sub("[^0-9.]", "", text))
return new_number * 1_000_000
elif bool(re.match("^[0-9.]+[Kk]{1}$", text)):
new_number = float(re.sub("[^0-9.]", "", text))
return new_number * 1_000
else:
return text # np.NaN
elif homo == True:
if bool(re.match("^[0-9.]+[MmKk]{1}$", text)):
return "parsed_number"
else:
return text # np.NaN
else:
raise Exception(f"invalid setting for homo attribute: {homo}")
df = pd.DataFrame({"test": ["3.1M", "2k", "not a number"]})
df['result'] = df['test'].apply(parse_numbers)
print(df)
结果:
test result
0 3.1M 3100000.0
1 2k 2000.0
2 not a number not a number
编辑:
我会写parse_numbers
有点不同
- 只有一行
re
。 - 使用
{,1}
将字符串也转换为数字"123"
- 最后转换为
integer
- 使用不带
s
的名称parse_number
因为它只解析一个数字。
import re
import pandas as pd
def parse_number(text, homo=False):
if not isinstance(homo, bool):
raise Exception(f"invalid setting for homo attribute: {homo}")
results = re.findall("^([0-9.]+)([MmKk]{,1})$", text)
#print(results)
if results:
if homo:
return "parsed_number"
else:
number, name = results[0]
new_number = float(number)
if name in ('M', 'm'):
new_number *= 1_000_000
elif name in ('K', 'k'):
new_number *= 1_000
return int(new_number)
else:
return text # np.NaN
df = pd.DataFrame({
"test": ["3.1M", "2k", "123", "not a number"]
})
df['result'] = df['test'].apply(parse_number)
df['homo'] = df['test'].apply(lambda x:parse_number(x, True))
print(df)
结果:
test result homo
0 3.1M 3100000 parsed_number
1 2k 2000 parsed_number
2 123 123 parsed_number
3 not a number not a number not a number
In [475]: alist = ["3.1M", "2k", "not a number"]
将您的函数直接应用于列表元素:
In [476]: [parse_numbers(s) for s in alist]
Out[476]: [3100000.0, 2000.0, 'not a number']
np.vectorize
如果您指定正确的 otypes 将起作用:
In [478]: fn = np.vectorize(parse_numbers, otypes=[object])
In [479]: fn(alist)
Out[479]: array([3100000.0, 2000.0, 'not a number'], dtype=object)
In [480]: timeit [parse_numbers(s) for s in alist]
12.9 µs ± 51.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [481]: timeit fn(alist)
24.1 µs ± 33.4 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
对于返回对象,frompyfunc
更快一些,没有一些 np.vectorize
开销:
In [482]: fn = np.frompyfunc(parse_numbers, 1,1)
In [483]: fn(alist)
Out[483]: array([3100000.0, 2000.0, 'not a number'], dtype=object)
In [484]: timeit fn(alist)
21.1 µs ± 68.5 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
我对其他 SO 的观察是 pandas
apply
慢得多。 pandas
在跟踪索引方面做了很多额外的工作。
对于基本上面向字符串和列表的任务,使用 numpy
几乎没有任何优势。
对于更大的列表,时间差异消失了:
In [485]: alist = alist*1000
In [486]: len(alist)
Out[486]: 3000
In [487]: timeit fn(alist)
12.5 ms ± 70.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [488]: timeit [parse_numbers(s) for s in alist]
12.3 ms ± 319 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [489]: fn = np.vectorize(parse_numbers, otypes=[object])
In [490]: timeit fn(alist)
12.1 ms ± 42.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)