4.6 从 CSV 文件加载数据
逗号分隔值 (CSV) 是一种广泛使用的数据存储格式。DGL 提供了 CSVDataset
用于加载和解析存储在 CSV 格式中的图数据。
创建 CSVDataset
对象
import dgl
ds = dgl.data.CSVDataset('/path/to/dataset')
返回的 ds
对象是一个标准的 DGLDataset
。例如,可以使用 __getitem__
获取图样本,也可以使用 ndata
/edata
获取节点/边特征。
# A demonstration of how to use the loaded dataset. The feature names
# may vary depending on the CSV contents.
g = ds[0] # get the graph
label = g.ndata['label']
feat = g.ndata['feat']
数据文件夹结构
/path/to/dataset/
|-- meta.yaml # metadata of the dataset
|-- edges_0.csv # edge data including src_id, dst_id, feature, label and so on
|-- ... # you can have as many CSVs for edge data as you want
|-- nodes_0.csv # node data including node_id, feature, label and so on
|-- ... # you can have as many CSVs for node data as you want
|-- graphs.csv # graph-level features
节点/边/图级别的数据存储在 CSV 文件中。meta.yaml
是一个元数据文件,指定从哪里读取节点/边/图数据以及如何解析它们以构建数据集对象。最小的数据文件夹包含一个 meta.yaml
和两个 CSV 文件,一个用于节点数据,一个用于边数据,在这种情况下,数据集只包含一个没有图级别数据的图。
无特征单图数据集
当数据集只包含一个没有节点或边特征的图时,数据文件夹中只需要三个文件:meta.yaml
,一个用于节点 ID 的 CSV 和一个用于边的 CSV。
./mini_featureless_dataset/
|-- meta.yaml
|-- nodes.csv
|-- edges.csv
meta.yaml
包含以下信息
dataset_name: mini_featureless_dataset
edge_data:
- file_name: edges.csv
node_data:
- file_name: nodes.csv
nodes.csv
在 node_id
字段下列出节点 ID
node_id
0
1
2
3
4
edges.csv
在两列(src_id
和 dst_id
)中列出所有边,指定每条边的源节点 ID 和目标节点 ID
src_id,dst_id
4,4
4,1
3,0
4,1
4,0
1,2
1,3
3,3
1,1
4,1
加载后,数据集包含一个没有任何特征的图
>>> import dgl
>>> dataset = dgl.data.CSVDataset('./mini_featureless_dataset')
>>> g = dataset[0] # only one graph
>>> print(g)
Graph(num_nodes=5, num_edges=10,
ndata_schemes={}
edata_schemes={})
注意
允许非整数节点 ID。构建图时,CSVDataset
会将每个原始 ID 映射到从零开始的整数 ID。如果节点 ID 已是从 0 到 num_nodes-1
的不同整数,则不应用映射。
注意
边始终是有向的。要获得双向边,请在边 CSV 文件中添加反向边或使用 AddReverse
转换加载的图。
一个没有任何特征的图通常不太有用。在下一个例子中,我们将展示如何加载和解析节点或边特征。
带特征和标签的单图数据集
当数据集包含一个带有节点或边特征及标签的单图时,数据文件夹中仍然只需要三个文件:meta.yaml
,一个用于节点 ID 的 CSV 和一个用于边的 CSV。
./mini_feature_dataset/
|-- meta.yaml
|-- nodes.csv
|-- edges.csv
meta.yaml
:
dataset_name: mini_feature_dataset
edge_data:
- file_name: edges.csv
node_data:
- file_name: nodes.csv
包含五个合成边数据(label
, train_mask
, val_mask
, test_mask
, feat
)的 edges.csv
src_id,dst_id,label,train_mask,val_mask,test_mask,feat
4,0,2,False,True,True,"0.5477868606453535, 0.4470617033458436, 0.936706701616337"
4,0,0,False,False,True,"0.9794634290792008, 0.23682038840665198, 0.049629338970987646"
0,3,1,True,True,True,"0.8586722047523594, 0.5746912787380253, 0.6462162561249654"
0,1,2,True,False,False,"0.2730008213674695, 0.5937484188166621, 0.765544096939567"
0,2,1,True,True,True,"0.45441619816038514, 0.1681403185591509, 0.9952376085297715"
0,0,0,False,False,False,"0.4197669213305396, 0.849983324532477, 0.16974127573016262"
2,2,1,False,True,True,"0.5495035052928215, 0.21394654203489705, 0.7174910641836348"
1,0,2,False,True,False,"0.008790817766266334, 0.4216530595907526, 0.529195480661293"
3,0,0,True,True,True,"0.6598715708878852, 0.1932390907048961, 0.9774471538377553"
4,0,1,False,False,False,"0.16846068931179736, 0.41516080644186737, 0.002158116134429955"
包含五个合成节点数据(label
, train_mask
, val_mask
, test_mask
, feat
)的 nodes.csv
node_id,label,train_mask,val_mask,test_mask,feat
0,1,False,True,True,"0.07816474278491703, 0.9137336384979067, 0.4654086994009452"
1,1,True,True,True,"0.05354099924658973, 0.8753101998792645, 0.33929432608774135"
2,1,True,False,True,"0.33234211884156384, 0.9370522452510665, 0.6694943496824788"
3,0,False,True,False,"0.9784264442230887, 0.22131880861864428, 0.3161154827254189"
4,1,True,True,False,"0.23142237259162102, 0.8715767748481147, 0.19117861103555467"
加载后,数据集包含一个图。节点/边特征以相同的列名存储在 ndata
和 edata
中。这个例子展示了如何使用双引号 "..."
包围的逗号分隔列表来指定向量形状的特征。
>>> import dgl
>>> dataset = dgl.data.CSVDataset('./mini_feature_dataset')
>>> g = dataset[0] # only one graph
>>> print(g)
Graph(num_nodes=5, num_edges=10,
ndata_schemes={'label': Scheme(shape=(), dtype=torch.int64), 'train_mask': Scheme(shape=(), dtype=torch.bool), 'val_mask': Scheme(shape=(), dtype=torch.bool), 'test_mask': Scheme(shape=(), dtype=torch.bool), 'feat': Scheme(shape=(3,), dtype=torch.float64)}
edata_schemes={'label': Scheme(shape=(), dtype=torch.int64), 'train_mask': Scheme(shape=(), dtype=torch.bool), 'val_mask': Scheme(shape=(), dtype=torch.bool), 'test_mask': Scheme(shape=(), dtype=torch.bool), 'feat': Scheme(shape=(3,), dtype=torch.float64)})
注意
默认情况下,CSVDataset
假定所有特征数据都是数值(例如,int、float、bool 或 list),并且不允许缺失值。用户可以为这些情况提供自定义数据解析器。有关更多详细信息,请参阅自定义数据解析器。
单异构图数据集
可以指定多个节点和边 CSV 文件(每种类型一个)来表示一个异构图。这里是一个包含两种节点类型和两种边类型的数据示例。
./mini_hetero_dataset/
|-- meta.yaml
|-- nodes_0.csv
|-- nodes_1.csv
|-- edges_0.csv
|-- edges_1.csv
meta.yaml
指定了每个 CSV 文件的节点类型名称(使用 ntype
)和边类型名称(使用 etype
)。边类型名称是一个字符串三元组,包含源节点类型名称、关系名称和目标节点类型名称。
dataset_name: mini_hetero_dataset
edge_data:
- file_name: edges_0.csv
etype: [user, follow, user]
- file_name: edges_1.csv
etype: [user, like, item]
node_data:
- file_name: nodes_0.csv
ntype: user
- file_name: nodes_1.csv
ntype: item
节点和边 CSV 文件遵循与同构图相同的格式。以下是一些用于演示目的的合成数据。
edges_0.csv
和 edges_1.csv
src_id,dst_id,label,feat
4,4,1,"0.736833152378035,0.10522806046048205,0.9418796835016118"
3,4,2,"0.5749339182767451,0.20181320245665535,0.490938012147181"
1,4,2,"0.7697294432580938,0.49397782380750765,0.10864079337442234"
0,4,0,"0.1364240150959487,0.1393107840629273,0.7901988878812207"
2,3,1,"0.42988138237505735,0.18389137408509248,0.18431292077750894"
0,4,2,"0.8613368738351794,0.67985810014162,0.6580438064356824"
2,4,1,"0.6594951663841697,0.26499036865016423,0.7891429392727503"
4,1,0,"0.36649684241348557,0.9511783938523962,0.8494919263589972"
1,1,2,"0.698592283371875,0.038622249776255946,0.5563827995742111"
0,4,1,"0.5227112950269823,0.3148264185956532,0.47562693094002173"
nodes_0.csv
和 nodes_1.csv
node_id,label,feat
0,2,"0.5400687466285844,0.7588441197954202,0.4268254673041745"
1,1,"0.08680051341900807,0.11446843700743892,0.7196969604886617"
2,2,"0.8964389655603473,0.23368113896545695,0.8813472954005022"
3,1,"0.5454703921677284,0.7819383771535038,0.3027939452162367"
4,1,"0.5365210052235699,0.8975240205792763,0.7613943085507672"
加载后,数据集包含一个带有特征和标签的异构图
>>> import dgl
>>> dataset = dgl.data.CSVDataset('./mini_hetero_dataset')
>>> g = dataset[0] # only one graph
>>> print(g)
Graph(num_nodes={'item': 5, 'user': 5},
num_edges={('user', 'follow', 'user'): 10, ('user', 'like', 'item'): 10},
metagraph=[('user', 'user', 'follow'), ('user', 'item', 'like')])
>>> g.nodes['user'].data
{'label': tensor([2, 1, 2, 1, 1]), 'feat': tensor([[0.5401, 0.7588, 0.4268],
[0.0868, 0.1145, 0.7197],
[0.8964, 0.2337, 0.8813],
[0.5455, 0.7819, 0.3028],
[0.5365, 0.8975, 0.7614]], dtype=torch.float64)}
>>> g.edges['like'].data
{'label': tensor([1, 2, 2, 0, 1, 2, 1, 0, 2, 1]), 'feat': tensor([[0.7368, 0.1052, 0.9419],
[0.5749, 0.2018, 0.4909],
[0.7697, 0.4940, 0.1086],
[0.1364, 0.1393, 0.7902],
[0.4299, 0.1839, 0.1843],
[0.8613, 0.6799, 0.6580],
[0.6595, 0.2650, 0.7891],
[0.3665, 0.9512, 0.8495],
[0.6986, 0.0386, 0.5564],
[0.5227, 0.3148, 0.4756]], dtype=torch.float64)}
多图数据集
当存在多个图时,可以包含一个额外的 CSV 文件来存储图级别特征。这里是一个例子。
./mini_multi_dataset/
|-- meta.yaml
|-- nodes.csv
|-- edges.csv
|-- graphs.csv
相应地,meta.yaml
应包含一个额外的 graph_data
键,以指示从哪个 CSV 文件加载图级别特征。
dataset_name: mini_multi_dataset
edge_data:
- file_name: edges.csv
node_data:
- file_name: nodes.csv
graph_data:
file_name: graphs.csv
为了区分不同图的节点和边,node.csv
和 edge.csv
必须包含一个额外的列 graph_id
。
edges.csv
:
graph_id,src_id,dst_id,feat
0,0,4,"0.39534097273254654,0.9422093637539785,0.634899790318452"
0,3,0,"0.04486384200747007,0.6453746567017163,0.8757520744192612"
0,3,2,"0.9397636966928355,0.6526403892728874,0.8643238446466464"
0,1,1,"0.40559906615287566,0.9848072295736628,0.493888090726854"
0,4,1,"0.253458867276219,0.9168191778828504,0.47224962583565544"
0,0,1,"0.3219496197945605,0.3439899477636117,0.7051530741717352"
0,2,1,"0.692873149428549,0.4770019763881086,0.21937428942781778"
0,4,0,"0.620118223673067,0.08691420300562658,0.86573472329756"
0,2,1,"0.00743445923710373,0.5251800239734318,0.054016385555202384"
0,4,1,"0.6776417760682221,0.7291568018841328,0.4523600060547709"
1,1,3,"0.6375445528248924,0.04878384701995819,0.4081642382536248"
1,0,4,"0.776002616178397,0.8851294998284638,0.7321742043493028"
1,1,0,"0.0928555079874982,0.6156748364694707,0.6985674921582508"
1,0,2,"0.31328748118329997,0.8326121496142408,0.04133991340612775"
1,1,0,"0.36786902637778773,0.39161865931662243,0.9971749359397111"
1,1,1,"0.4647410679872376,0.8478810655406659,0.6746269314422184"
1,0,2,"0.8117650553546695,0.7893727601272978,0.41527155506593394"
1,1,3,"0.40707309111756307,0.2796588354307046,0.34846782265758314"
1,1,0,"0.18626464175355095,0.3523777809254057,0.7863421810531344"
1,3,0,"0.28357022069634585,0.13774964202156292,0.5913335505943637"
nodes.csv
:
graph_id,node_id,feat
0,0,"0.5725330322207948,0.8451870383322376,0.44412796119211184"
0,1,"0.6624186423087752,0.6118386331195641,0.7352138669985214"
0,2,"0.7583372765843964,0.15218126307872892,0.6810484348765842"
0,3,"0.14627522432017592,0.7457985352827006,0.1037097085190507"
0,4,"0.49037522512771525,0.8778998699783784,0.0911194482288028"
1,0,"0.11158102039672668,0.08543289788089736,0.6901745368284345"
1,1,"0.28367647637469273,0.07502571020414439,0.01217200152200748"
1,2,"0.2472495901894738,0.24285506608575758,0.6494437360242048"
1,3,"0.5614197853127827,0.059172654879085296,0.4692371689047904"
1,4,"0.17583413999295983,0.5191278830882644,0.8453123358491914"
graphs.csv
包含一个 graph_id
列和任意数量的特征列。这里的示例数据集包含两个图,每个图都有 feat
和 label
图级别数据。
graph_id,feat,label
0,"0.7426272601929126,0.5197462471155317,0.8149104951283953",0
1,"0.534822233529295,0.2863627767733977,0.1154897249106891",0
加载后,数据集包含多个带有特征和标签的同构图
>>> import dgl
>>> dataset = dgl.data.CSVDataset('./mini_multi_dataset')
>>> print(len(dataset))
2
>>> graph0, data0 = dataset[0]
>>> print(graph0)
Graph(num_nodes=5, num_edges=10,
ndata_schemes={'feat': Scheme(shape=(3,), dtype=torch.float64)}
edata_schemes={'feat': Scheme(shape=(3,), dtype=torch.float64)})
>>> print(data0)
{'feat': tensor([0.7426, 0.5197, 0.8149], dtype=torch.float64), 'label': tensor(0)}
>>> graph1, data1 = dataset[1]
>>> print(graph1)
Graph(num_nodes=5, num_edges=10,
ndata_schemes={'feat': Scheme(shape=(3,), dtype=torch.float64)}
edata_schemes={'feat': Scheme(shape=(3,), dtype=torch.float64)})
>>> print(data1)
{'feat': tensor([0.5348, 0.2864, 0.1155], dtype=torch.float64), 'label': tensor(0)}
如果 graphs.csv
中只有一个特征列,data0
将直接是该特征的张量。
自定义数据解析器
默认情况下,CSVDataset
假定所有存储的节点/边/图级别数据都是数值。用户可以为 CSVDataset
提供自定义的 DataParser
来处理更复杂的数据类型。DataParser
需要实现 __call__
方法,该方法接受从 CSV 文件创建的 pandas.DataFrame
对象,并应返回一个解析后的特征数据字典。解析后的特征数据将保存到相应 DGLGraph
对象的 ndata
和 edata
中,因此必须是张量或 numpy 数组。下面展示了一个将字符串类型标签转换为整数的 DataParser
示例。
假设有如下数据集:
./customized_parser_dataset/
|-- meta.yaml
|-- nodes.csv
|-- edges.csv
meta.yaml
:
dataset_name: customized_parser_dataset
edge_data:
- file_name: edges.csv
node_data:
- file_name: nodes.csv
edges.csv
:
src_id,dst_id,label
4,0,positive
4,0,negative
0,3,positive
0,1,positive
0,2,negative
0,0,positive
2,2,negative
1,0,positive
3,0,negative
4,0,positive
nodes.csv
:
node_id,label
0,positive
1,negative
2,positive
3,negative
4,positive
为了解析字符串类型标签,可以定义一个 DataParser
类如下:
import numpy as np
import pandas as pd
class MyDataParser:
def __call__(self, df: pd.DataFrame):
parsed = {}
for header in df:
if 'Unnamed' in header: # Handle Unnamed column
print("Unnamed column is found. Ignored...")
continue
dt = df[header].to_numpy().squeeze()
if header == 'label':
dt = np.array([1 if e == 'positive' else 0 for e in dt])
parsed[header] = dt
return parsed
使用定义的 DataParser
创建 CSVDataset
>>> import dgl
>>> dataset = dgl.data.CSVDataset('./customized_parser_dataset',
... ndata_parser=MyDataParser(),
... edata_parser=MyDataParser())
>>> print(dataset[0].ndata['label'])
tensor([1, 0, 1, 0, 1])
>>> print(dataset[0].edata['label'])
tensor([1, 0, 1, 1, 0, 1, 0, 1, 0, 1])
注意
要为不同的节点/边类型指定不同的 DataParser
,可以将一个字典传递给 ndata_parser
和 edata_parser
,其中键是类型名称(节点类型为单个字符串;边类型为字符串三元组),值是要使用的 DataParser
。
完整的 YAML 规范
CSVDataset
允许对加载和解析过程进行更灵活的控制。例如,可以通过 meta.yaml
更改 ID 列名。下面的示例列出了所有支持的键。
version: 1.0.0
dataset_name: some_complex_data
separator: ',' # CSV separator symbol. Default: ','
edge_data:
- file_name: edges_0.csv
etype: [user, follow, user]
src_id_field: src_id # Column name for source node IDs. Default: src_id
dst_id_field: dst_id # Column name for destination node IDs. Default: dst_id
- file_name: edges_1.csv
etype: [user, like, item]
src_id_field: src_id
dst_id_field: dst_id
node_data:
- file_name: nodes_0.csv
ntype: user
node_id_field: node_id # Column name for node IDs. Default: node_id
- file_name: nodes_1.csv
ntype: item
node_id_field: node_id # Column name for node IDs. Default: node_id
graph_data:
file_name: graphs.csv
graph_id_field: graph_id # Column name for graph IDs. Default: graph_id
顶层
在顶层,只有 6 个键可用
version
: 可选。字符串。指定使用的meta.yaml
版本。将来可能会添加更多功能。
dataset_name
: 必填。字符串。指定数据集名称。
separator
: 可选。字符串。指定如何解析 CSV 文件中的数据。默认值:','
。
edge_data
: 必填。EdgeData
列表。用于解析边 CSV 文件的元数据。
node_data
: 必填。NodeData
列表。用于解析节点 CSV 文件的元数据。
graph_data
: 可选。GraphData
。用于解析图 CSV 文件的元数据。
EdgeData
有 4 个键
file_name
: 必填。字符串。要加载数据的 CSV 文件。
etype
: 可选。字符串列表。边类型名称采用字符串三元组形式:[源节点类型, 关系类型, 目标节点类型]。
src_id_field
: 可选。字符串。用于读取源节点 ID 的列。默认值:src_id
。
dst_id_field
: 可选。字符串。用于读取目标节点 ID 的列。默认值:dst_id
。
NodeData
有 3 个键
file_name
: 必填。字符串。要加载数据的 CSV 文件。
ntype
: 可选。字符串。节点类型名称。
node_id_field
: 可选。字符串。用于读取节点 ID 的列。默认值:node_id
。
GraphData
有 2 个键
file_name
: 必填。字符串。要加载数据的 CSV 文件。
graph_id_field
: 可选。字符串。用于读取图 ID 的列。默认值:graph_id
。