Julia:将 DataFrame 传递给函数会创建指向 DataFrame 的指针?
Posted
技术标签:
【中文标题】Julia:将 DataFrame 传递给函数会创建指向 DataFrame 的指针?【英文标题】:Julia: Passing a DataFrame to a function creates a pointer to the DataFrame? 【发布时间】:2015-03-18 05:01:23 【问题描述】:我有一个函数可以标准化 DataFrame 的前 N 列。我想返回规范化的 DataFrame,但不要理会原始数据。然而,该函数似乎也改变了传递的 DataFrame!
using DataFrames
function normalize(input_df::DataFrame, cols::ArrayInt)
norm_df = input_df
for i in cols
norm_df[i] = (input_df[i] - minimum(input_df[i])) /
(maximum(input_df[i]) - minimum(input_df[i]))
end
norm_df
end
using RDatasets
iris = dataset("datasets", "iris")
println("original df:\n", head(iris))
norm_df = normalize(iris, [1:4]);
println("should be the same:\n", head(iris))
输出:
original df:
6x5 DataFrame
| Row | SepalLength | SepalWidth | PetalLength | PetalWidth | Species |
|-----|-------------|------------|-------------|------------|----------|
| 1 | 5.1 | 3.5 | 1.4 | 0.2 | "setosa" |
| 2 | 4.9 | 3.0 | 1.4 | 0.2 | "setosa" |
| 3 | 4.7 | 3.2 | 1.3 | 0.2 | "setosa" |
| 4 | 4.6 | 3.1 | 1.5 | 0.2 | "setosa" |
| 5 | 5.0 | 3.6 | 1.4 | 0.2 | "setosa" |
| 6 | 5.4 | 3.9 | 1.7 | 0.4 | "setosa" |
should be the same:
6x5 DataFrame
| Row | SepalLength | SepalWidth | PetalLength | PetalWidth | Species |
|-----|-------------|------------|-------------|------------|----------|
| 1 | 0.222222 | 0.625 | 0.0677966 | 0.0416667 | "setosa" |
| 2 | 0.166667 | 0.416667 | 0.0677966 | 0.0416667 | "setosa" |
| 3 | 0.111111 | 0.5 | 0.0508475 | 0.0416667 | "setosa" |
| 4 | 0.0833333 | 0.458333 | 0.0847458 | 0.0416667 | "setosa" |
| 5 | 0.194444 | 0.666667 | 0.0677966 | 0.0416667 | "setosa" |
| 6 | 0.305556 | 0.791667 | 0.118644 | 0.125 | "setosa" |
【问题讨论】:
【参考方案1】:Julia 使用一种称为“传递共享”的行为。来自文档(强调我的):
Julia 函数参数遵循有时称为“传递共享”的约定,这意味着将值传递给函数时不会复制它们。函数参数本身充当新的变量绑定(可以引用值的新位置),但它们引用的值与传递的值相同。 在函数中对可变值(例如数组)所做的修改将对调用者可见。 这与在 Scheme、大多数 Lisps、Python、Ruby 和 Perl 以及其他动态语言中发现的行为相同。
在您的特定情况下,您似乎想要做的是为您的规范化操作创建一个全新且独立的 DataFrame。有两个操作可以做到这一点:copy
和 deepcopy
。如果所有 DataFrame 列的元素类型都是不可变的(例如 Int
、Float64
、String
等),那么 copy
就足够了。但是,如果其中一列包含可变类型,则需要使用deepcopy
。函数调用如下所示:
norm_df = copy(input_df) # Column types are immutable
norm_df = deepcopy(input_df) # At least one column type is mutable
Julia 通常会要求您明确地执行这些操作,因为创建大型数据框的独立副本可能会耗费大量计算资源,而 Julia 是一种面向性能的语言。
对于那些想要了解更多关于 copy
和 deepcopy
之间区别的详细信息的人,请再次从文档中注意以下几点:
copy(x)
:创建 x 的浅拷贝:复制外部结构,但不复制所有内部值。例如,复制一个数组会生成一个新数组,其元素与原始数组相同。
deepcopy(x)
:创建 x 的深层副本:一切都被递归复制,从而产生一个完全独立的对象。例如,深拷贝一个数组会产生一个新数组,其元素是原始元素的深拷贝。
类型DataFrame
类似于数组,因此如果元素是可变的,则deepcopy
是必需的。如果您不确定,请使用deepcopy
(虽然它会更慢)。
一个相关的 SO 问题是here。
【讨论】:
谢谢!那么在哪些情况下,对象是复制还是深度复制?例如,如果我这样做,iris2 = iris
然后将iris2
传递给normalize()
,iris
是否也应该被修改?
@AlexanderFlyax 是的,iris
会被修改。通常y=x
形式的任何操作不会 创建一个新的独立对象,无论类型如何(尽管有趣的是,对于向量,y=x[1:end]
将(我认为)创建一个独立的对象)。对于像Int64
这样的具体类型,copy
会创建一个新的独立对象,但是对于ArrayInt64
,copy
只创建一个独立的外部结构,而不是独立的内部元素,因此需要deepcopy
处理数组时。 DataFrame
类似于数组,因此需要deepcopy
。
感谢您的解释。我并不是要批评 Julia,只是想了解:如果修改副本会修改原始数组,为什么我需要制作数组的副本?如果我想修改原始数组,我会这样做,不是吗?即,它似乎充其量是多余的,最坏的情况是适得其反。我错过了什么吗?
@AlexanderFlyax 好问题。我不知道!我最好的猜测是它围绕范围旋转,即在某些情况下,在一个范围内拥有一组引用(例如调用函数)同时在不同的范围内拥有一组独立的引用(例如称为函数),但两组引用都指向内存中的同一个对象。请不要把这当成福音。也许您可以将该问题作为一个新的 SO 问题提出(不一定是 Julia 特定的)并获得更明智的答复...以上是关于Julia:将 DataFrame 传递给函数会创建指向 DataFrame 的指针?的主要内容,如果未能解决你的问题,请参考以下文章
R语言ggplot2可视化:将dataframe和数据列名称传递给函数通过函数进行ggplot2可视化输出
Julia 将 DataFrame 写入 csv 失败 UndefRefError: access to undefined reference