Pandas DataFrame:无法将字符串转换为浮点数

4

我有一个pandas数据框中的列Column1,类型为str,其中的值如下所示:

import pandas as pd
df = pd.read_table("filename.dat")
type(df["Column1"].ix[0])   #outputs 'str'
print(df["Column1"].ix[0])

这段代码输出'1/350',当前是一个字符串。我想将它转换成浮点数。

我尝试了以下代码:

df["Column1"] = df["Column1"].astype('float64', raise_on_error = False)

但这并没有把值转换为浮点数。
这个也失败了:
df["Column1"] = df["Column1"].convert_objects(convert_numeric=True)

这个操作失败了:

df["Column1"] = df["Column1"].apply(pd.to_numeric, args=('coerce',))

我该如何将“Column1”列中的所有值转换为浮点数?我能否使用正则表达式来去掉括号?

编辑:

以下是代码行:

df["Meth"] = df["Meth"].apply(eval)

这个方法可以起作用,但是只有当我使用它两次时才有效,也就是说

df["Meth"] = df["Meth"].apply(eval)
df["Meth"] = df["Meth"].apply(eval)

为什么会出现这种情况?
3个回答

4

为了得到结果,您需要评估表达式(例如'1/350'),可以使用Python的eval()函数。

通过将Panda的apply()函数包装在其周围,然后就可以在列中的每个值上执行eval()函数。例:

df["Column1"].apply(eval)

作为字面量的解释器,您也可以像文档中所述使用ast.literal_eval函数。更新:这将不起作用,因为literal_eval()的使用仍然受到限制,仅限于加法和减法(来源)。
备注:正如其他答案和评论中提到的,使用eval()并非没有风险,因为您基本上执行传递的任何输入。换句话说,如果您的输入包含恶意代码,您正在免费让它运行。
替代选项:
# Define a custom div function
def div(a,b):
    return int(a)/int(b)

# Split each string and pass the values to div
df_floats = df['col1'].apply(lambda x: div(*x.split('/')))

第二种选择,在数据不干净的情况下:

通过使用正则表达式,我们可以删除出现在分子之前和分母之后的任何非数字字符。

# Define a custom div function (unchanged)
def div(a,b):
    return int(a)/int(b)

# We'll import the re module and define a precompiled pattern
import re
regex = re.compile('\D*(\d+)/(\d+)\D*')

df_floats = df['col1'].apply(lambda x: div(*regex.findall(x)[0]))

我们会失去一些性能,但好处是即使输入像'!erefdfs?^dfsdf1/350dqsd qsd qs d'这样的内容,我们仍然得到了1/350的值。
性能:
在使用有100,000行的数据框时,第二个选项(使用用户定义的div函数)明显获胜:
- 使用eval:1次循环,3个中的最佳表现:每次1.41秒 - 使用div:10次循环,3个中的最佳表现:每次159毫秒 - 使用re:1次循环,3个中的最佳表现:每次275毫秒

这是一个字符串。我想将它转换成浮点数。 - ShanZhengYang
没问题 :) 谢谢你的帮助! - ShanZhengYang
1
对于基准测试的加1,我一开始很惊讶eval比较慢,但也许这是有道理的。eval需要考虑可能传入的任何代码,而你的替代方案明确地在/上工作。 - Jeff
以上 div 函数(我喜欢)的错误是 ValueError: invalid literal for int() with base 10: "'9",我不确定如何修复它。 - ShanZhengYang
看起来数据格式有一些错误,有一个额外的单引号。这可能意味着还有其他问题,但在这种情况下,在这个答案中将其更改为return int(a.strip("'")/int(b.strip("'"))-即双引号,单引号,双引号。应该会解决它。 - Jeff
更新了答案,提供了一种基于正则表达式的替代方案,以删除字符串开头或结尾的任何不需要的字符(非数字)。 - DocZerø

3
我不主张使用eval。我本不想花时间回答这个问题,但我感到有必要这样做,因为我不希望你使用eval
所以,我写了这个函数,它适用于pd.Series
def do_math_in_string(s):
    op_map = {'/': '__div__', '*': '__mul__', '+': '__add__', '-': '__sub__'}
    df = s.str.extract(r'(\d+)(\D+)(\d+)', expand=True)
    df = df.stack().str.strip().unstack()
    df.iloc[:, 0] = pd.to_numeric(df.iloc[:, 0]).astype(float)
    df.iloc[:, 2] = pd.to_numeric(df.iloc[:, 2]).astype(float)
    def do_op(x):
        return getattr(x[0], op_map[x[1]])(x[2])
    return df.T.apply(do_op)

演示

s = pd.Series(['1/2', '3/4', '4/5'])

do_math_in_string(s)

0    0.50
1    0.75
2    0.80
dtype: float64

do_math_in_string(pd.Series(['1/2', '3/4', '4/5', '6+5', '11-7', '9*10']))

0     0.50
1     0.75
2     0.80
3    11.00
4     4.00
5    90.00
dtype: float64

请不要使用eval

如果你现在避免这种情况,以后的生活中你会有更好的自尊心。为什么呢? - ShanZhengYang
1
那个评论是我在滑稽夸张。但实际上,使用 eval 是一种导致代码难以管理、维护、调试和保持安全的方法。它通常被认为是不良实践。 - piRSquared
如果他没有处理用户输入,那么在这里使用 eval 会有什么问题? - Jeff
我再说几句:一,输入文件可以是用户输入。二,这仍然会使跟踪错误和维护变得更加困难(我的看法)。三,所有这些都是编码哲学,实际上并不重要,因为 OP 可以按照自己的意愿去做。无论对于 eval 的意见如何,我的答案仍然能够达到其目的。 - piRSquared
我其实只是好奇是否还有比它评估用户输入的问题更多的东西。然而,对于 OP 来说,需要注意的是 eval 如果假设这不涉及用户输入,那么可能会存在安全问题。 - Jeff
@JeffL。@piRSquared 我发现使用 eval 时出现了奇怪的行为。请参见上面的编辑。我希望你的函数能够正常工作 :/ - ShanZhengYang

2

您可以通过将eval应用于列来实现:

data = {'one':['1/20', '2/30']}
df = pd.DataFrame(data)

In [8]: df['one'].apply(eval)
Out[8]:
0    0.050000
1    0.066667
Name: one, dtype: float64

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接