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
edges.csv
包含五个合成的边缘数据 (label
, train_mask
, val_mask
, test_mask
, feat
):
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"
nodes.csv
包含五个合成节点数据 (label
, train_mask
, val_mask
, test_mask
, feat
):
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)})
注意
默认情况下,CSVDatatset
假设所有特征数据都是数值(例如,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
假设所有存储的节点/边/图级数据都是数值。用户可以提供自定义的 DataParser
给 CSVDataset
来处理更复杂的数据类型。一个 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
: Required. String. The CSV file to load data from.
graph_id_field
: 可选。字符串。 用于读取图形ID的列。默认值:graph_id
。