如何在Julia中创建一个关联矩阵

5

我希望创建一个关联矩阵。


我有一个包含3列的文件,如下:

id x  y   
A  22   2   
B   4  21   
C  21 360   
D  26   2   
E  22  58   
F   2 347   

我希望你能为我提供一个像下面这样的矩阵(不包含列和行名称):
  2 4 21 22 26 58 347 360   
A 1 0  0  1  0  0   0   0   
B 0 1  1  0  0  0   0   0   
C 0 0  1  0  0  0   0   1   
D 1 0  0  0  1  0   0   0   
E 0 0  0  1  0  1   0   0   
F 1 0  0  0  0  0   1   0   

我已经开始写代码了,如下所示:

haps = readdlm("File.txt",header=true)      
hap1_2 = map(Int64,haps[1][:,2:end])    
ID = (haps[1][:,1])                      
dic1 = Dict()

for (i in 1:21)
    dic1[ID[i]] = hap1_2[i,:]
end

X=[zeros(21,22)];       #the original file has 21 rows and 22 columns 
X1 = hcat(ID,X)

现在的问题是我不知道如何像上面的例子一样在特定的列中填充矩阵,同时我也不确定我的做法是否正确。有什么建议可以帮助我吗?谢谢!

你是否尝试过DataFrames包中的ModelFrame()ModelMatrix()函数?它们可能具有你所需的功能。此外,还有一个sparse()函数可用于创建稀疏矩阵(这就是你需要创建的)。这个函数虽然能用,但实现起来比较复杂。 - Michael Ohlrogge
1
关于关联矩阵,x=2和y=2的相同列是否是有意为之? - Dan Getz
为了类型推断和效率的考虑,最好将单个特定类型(如Int或Bool)的关联矩阵保持一致,而不要混合字符串标签。 - Dan Getz
2个回答

2

NamedArrays 是一个很棒的包,可以为行和列命名,并且似乎符合这个问题的要求。假设数据在 data.csv 中,以下是一种解决方法(使用 Pkg.add("NamedArrays") 安装 NamedArrays):

data,header = readcsv("data.csv",header=true);
# get the column names by looking at unique values in columns
cols = unique(vec([(header[j+1],data[i,j+1]) for i in 1:size(data,1),j=1:2]))
# row names from ID column
rows = data[:,1]

using NamedArrays
narr = NamedArray(zeros(Int,length(rows),length(cols)),(rows,cols),("id","attr"));
# now stamp in the 1s in the right places
for r=1:size(data,1),c=2:size(data,2) narr[data[r,1],(header[c],data[r,c])] = 1 ; end

现在我们有以下内容(注意我为了更好的打印结果已经转置了narr):
julia> narr'
10x6 NamedArray{Int64,2}:
attr ╲ id │ A  B  C  D  E  F
──────────┼─────────────────
("x",22)  │ 1  0  0  0  1  0
("x",4)   │ 0  1  0  0  0  0
("x",21)  │ 0  0  1  0  0  0
("x",26)  │ 0  0  0  1  0  0
("x",2)   │ 0  0  0  0  0  1
("y",2)   │ 1  0  0  1  0  0
("y",21)  │ 0  1  0  0  0  0
("y",360) │ 0  0  1  0  0  0
("y",58)  │ 0  0  0  0  1  0
("y",347) │ 0  0  0  0  0  1

但是,如果需要使用DataFrames,类似的技巧也应该适用。

---------- 更新 ----------

如果应该忽略值的列,即x=2和y=2都应在值为2的列上设置1,则代码变为:

using NamedArrays
data,header = readcsv("data.csv",header=true);
rows = data[:,1]
cols = map(string,sort(unique(vec(data[:,2:end]))))
narr = NamedArray(zeros(Int,length(rows),length(cols)),(rows,cols),("id","attr"));
for r=1:size(data,1),c=2:size(data,2) narr[data[r,1],string(data[r,c])] = 1 ; end

提供:

julia> narr
6x8 NamedArray{Int64,2}:
id ╲ attr │   2    4   21   22   26   58  347  360
──────────┼───────────────────────────────────────
A         │   1    0    0    1    0    0    0    0
B         │   0    1    1    0    0    0    0    0
C         │   0    0    1    0    0    0    0    1
D         │   1    0    0    0    1    0    0    0
E         │   0    0    0    1    0    1    0    0
F         │   1    0    0    0    0    0    1    0

非常感谢。 更新版本正是我所寻找的。 有没有办法只打印矩阵,而不包括列和行名称? 谢谢! - godines
NamedArrays是由常规数组支持的,这意味着您可以使用很少的开销将它们转换为常规矩阵。使用:array(narr) - Dan Getz
您也可以选择不使用NamedArrays,而只是使用“rows”、“cols”变量并创建一个常规数组。使用“narr = zeros(Int,length(rows),length(cols))”。唯一棘手的部分是找到正确的列来填充矩阵。如果您选择这条路线,您将需要某种查找表(或Dict)。 - Dan Getz

1
这是我用于将分类变量转换为稀疏矩阵进行回归分析的函数,稍作改动。该函数包含各种注释和选项,可根据需要进行调整。请注意:按照目前的写法,它将x和y中出现的“2”和“21”视为不同。它的命名和外观远不如Dan Getz的优美响应。但它的主要优势在于它可以与稀疏矩阵一起使用,因此如果您的数据很大,它将有助于减少存储空间和计算时间。
function OneHot(x::Array, header::Bool)
    UniqueVals = unique(x)
    Val_to_Idx = [Val => Idx for (Idx, Val) in enumerate(unique(x))] ## create a dictionary that maps unique values in the input array to column positions in the new sparse matrix.
    ColIdx = convert(Array{Int64}, [Val_to_Idx[Val] for Val in x])
    MySparse = sparse(collect(1:length(x)),  ColIdx, ones(Int32, length(x)))
    if header
        return [UniqueVals' ; MySparse]  ## note: this won't be sparse
        ## alternatively use return (MySparse, UniqueVals) to get a tuple, second element is the header which you can then feed to something to name the columns or do whatever else with
    else
        return MySparse ## use MySparse[:, 2:end] to drop a value (which you would want to do for categorical variables in a regression)
    end
end

x = [22, 4, 21, 26, 22, 2];
y = [2, 21, 360, 2, 58, 347];

Incidence = [OneHot(x, true) OneHot(y, true)]

7x10 Array{Int64,2}:
 22  4  21  26  2  2  21  360  58  347
  1  0   0   0  0  1   0    0   0    0
  0  1   0   0  0  0   1    0   0    0
  0  0   1   0  0  0   0    1   0    0
  0  0   0   1  0  1   0    0   0    0
  1  0   0   0  0  0   0    0   1    0
  0  0   0   0  1  0   0    0   0    1

谢谢你的帮助。唯一的问题是我不想在第一行中有重复的值(例如2 2)。 - godines
@GersonOliveiraJunior 好的,没问题。看起来 Dan 的解决方案更符合你的要求。这只是我从另一个项目中提取的一小段代码,我觉得如果有用的话就放出来了。 - Michael Ohlrogge

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