
概述
在之前的两篇文章中,我们讨论了对以太坊常规数据的导入和分析。文章链接如下:
如果您未阅读过以上文章并不影响您继续阅读本篇文章,本文内所有数据集均提供下载链接。
事实上,以太坊内存在复杂的交易关系,这些交易关系构成了一个庞大的图。如果需要分析这些复杂交易,我们需要进行图数据分析。
本文主要介绍如何进行基于以太坊数据进行图数据分析,主要包括以下内容:
- 图可视化
- 图检索
对于图可视化,我选择了 CosmosGraph 作为主要工具,而对于图检索,我选择了 cozo 图数据库。当然,读者也可以使用 neo4j
或其他图数据库。
图可视化
在本节中,我们主要针对 ERC-20 代币的转账进行可视化处理。此处我们选择使用基于 WebGL
的可在浏览器内运行的高性能图可视化工具 CosmosGraph
。CosmosGraph
要求我们使用如下格式导入数据:
1 | time, source, target, value |
其中,time
和 value
是可选的,time
用于添加时间轴,如下图:
最下方的即为根据 time
生成的时间轴。我们可以在 csv
中增加任意数据用于生成节点大小或指定线条颜色等。
我们需要在 clickhouse
中导出相关数据:
1 | SELECT |
由于 topics
中的 from
和 to
均为 uint256
数据类型,而实际上 address
为 uint160
类型,这导致原始数据存在大量前导 0 ,此处我们使用 substr
函数去除前导 0 以方便阅读。为了区分不同代币转账,我们使用 concat('#', substr(hex(cityHash64(symbol)), 11))
拼接得到形如 #5EC1A6
的 16 进制 RGB 颜色。
我们可以通过 DBeaver
附带的工具进行数据导出:
导出时所有对话框均可为默认配置即可,只需要记住 csv
导出地址即可。
导出数据可以使用 此链接 进行下载
打开 此网站 ,点击 Select data file
,选择之前导出的数据集,如下:
在此处,我们设置节点大小由链接数目决定,节点颜色由转账总价值决定,而链接颜色由 linkColor
决定,点击 Launch
启动渲染。最后,我们可以得到如下图像:
读者可以根据自身需求研究可视化图像。如果读者希望对 CosmosGraph
有更多了解,可以参考 How to Visualize a Graph with a Million Nodes
图分析
目前市面上存在了大量图数据库,其中的代表是 neo4j
图数据库,但在本文中,我们准备使用 cozodb
作为图数据库。原因有以下几点:
- 嵌入式数据库,易于安装且轻量化
- 可以处理百万量级内的图数据集,对于当下的项目是完全够用的
- 开发者是国人,其全部文档均有汉语版本
- 使用 Rust 编写
但是,就目前体验而言,cozodb
的缺点如下:
- 使用特定的数据重新语言(
CozoScript
),而不是常用的Cypher
等常用的图数据查询语言 - 查询语言的错误显示较差,不会具体显示查询语言错误的具体位置,Debug 比较令人烦恼
如果读者希望获得更多关于 cozodb
的信息,可以访问 文档。
安装 cozodb
cozodb
的开发者在 Github Realse 中提供了已经编译好的二进制文件,
读者可根据自身要求选择合适架构的编译产物,使用 curl
或 wget
下载到服务器后,命令如下:
1 | wget https://github.com/cozodb/cozo/releases/download/v0.5.1/cozo-0.5.1-x86_64-unknown-linux-musl.gz |
使用以下命令进行解压缩:
1 | gunzip cozo-0.5.1-x86_64-unknown-linux-musl.gz |
然后进行重命名和权限调整:
1 | mv cozo-0.5.1-x86_64-unknown-linux-musl cozoserver |
可以使用以下命令测试是否安装成果:
1 | ./cozoserver -V |
数据清洗
我们首先需要准备数据导出的 SQL
文件,使用 vi export.sql
创建并键入以下代码:
1 | SELECT |
在将数据导入图数据之前,我们需要在 clickhouse
中导出需要的数据,我们直接在服务器内使用以下命令导出数据并保存为 ERC20cozo.csv
文件:
1 | clickhouse-client --queries-file export.sql -f CSVWithNames > ERC20cozo.csv |
使用此命令可能会出现
. (AUTHENTICATION_FAILED)
报错,此时读者需要增加--password
参数手动输入用户(默认为deafult
)密码
如果您没有完成之前的系列教程,但对于图数据库感兴趣,您可以直接使用 此链接 下载数据集。
使用以下命令检查数据情况:
1 | head ERC20cozo.csv |
接下来,我们需要执行数据导入工作,首先使用以下命令启动 cozodb
命令行:
1 | ./cozoserver -r |
由于我们需要多行输入导入指令,所以我们首先键入空格后进行回车进入多行模式,然后输入以下内容:
1 | res[txHash,txFrom,txTo,txValue,symbol] <~ |
由于终端比较难以进行大量内容键入,我建议读者在文本编辑器内编写相关指令,然后复制到终端内运行
如果没有问题,读者会受到如下回复:
1 | status |
接下来,我们讨论上述指令是如何导入数据的。
此处我们讨论的 cozoscript
是 Datalog
的一种方言,所以其规则与 SQL
差异较大。
关于
Datalog
语言的一些基础问题,读者可阅读 An introduction to Datalog 博客
在数据导入过程中,我们使用了 csv
文件阅读函数 CsvReader
,其具体的相关参数请自行参考 文档。其中 types
用来指定数据类型,cozodb
支持的数据类型请参考 此文档
此处使用了 res
作为暂时存储数据的表(事实上,在文档中一般使用 规则 描述)。我们也可以看到此处使用了 <~
连接 CsvReader
,原因在于 CsvReader
为 固定规则(可以理解为是一张不可变的表)。
此处的操作简单来说,就是将数据从不可变的
CsvReader
通过<~
操作符转移到可修改的res
表格以方便数据清洗
?[...] := res[...]
事实上就是以下 SQL 代码:
1 | SELECT ... |
所有使用 ?
开始的语句都是数据查询语句,并会将结果进行输出,我们会在后文更加详细的展示此类语句的用法。
最后使用 :replace
实现对查询数据的存储,关于 :replace
的更多信息请参考 存储表
数据查询
在完成数据导入后,我们开始聚焦于从原始数据中进行查询以提取获得更多有效的信息。我们首先展示进行一些常规的数据查询,这些查询在关系型数据库中也可以高效完成。
第一个任务是获取 ERC20 代币最大转移量的前五个交易的哈希值、数量和代币名称:
1 | ?[txHash, txValue, symbol] := *ERC20trans{txHash, txValue, symbol} |
输出如下:
1 | txHash | txValue | symbol |
此处的 *ERC20trans{...}
意为从 ERC20trans
表格内进行数据读取,同时在花括号内指定了列的读取情况。我们也使用了 :order
进行数据排序和 :limit
限制输出范围。我们可以看到该语法与 SQL 在思想上有一定相似性。
接下来,我们基于以上检索进行优化,我们希望输出的交易中 symbol
都不为空值:
1 | ?[txHash, txValue, symbol] := |
此处我们使用了 symbol != ""
作为限制条件对检索结果进行限制。结果如下:
1 | txHash | txValue | symbol |
获取 ERC20 代币转出最多的地址:
1 | ?[txFrom, count(txFrom)] := *ERC20trans{txFrom} |
此处相当于进行了 GROUP BY
操作,此操作被称为 聚合,具体的介绍可以参考 文档
此查询返回值如下:
1 | txFrom | count(txFrom) |
笔者使用
clickhouse
进行聚合操作的结果与之相同
此处的 a5025faba6e70b84f74e9b1113e5f7f4e7f4859f
是一个 multisender.app 的地址,该应用用来大批量转账。而 a9d1e08c7793af67e9d92fe308d5697fb81d3e43
是知名交易所 Coinbase
的地址。我们接下来主要分析 Coinbase
地址的一系列特征。
检索 Coinbase
地址转出的最多的五种代币:
1 | ?[symbol, count(symbol), sum(txValue)] := |
输出如下:
1 | symbol | count(symbol) | sum(txValue) |
在此处,我们需要说明所有 :=
后的语句会构成一个规则集,cozodb
会使用此规则集查询我们需要的数据,在上文中,我们使用了 ,
连接规则集,这意味各条规则之间术语 并 的关系。
上述给出的一系列查询在关系型数据库中使用 SQL 也可以简单的完成,始终没有涉及 cozodb
的关键功能,即对于图检索的支持。继续分析上述 coinbase
地址,我们需要知道从 coinbase
转出资产的人又在哪里消费了这些资产,如下图:
我们的目标是找出 interactiveAddress
地址,查询代码如下:
1 | ?[interactiveAddress, count(interactiveAddress)] := |
返回结果如下:
1 | interactiveAddress | count(interactiveAddress) |
在上述检索中,我们使用了所谓的 绑定 ,即将某一个数据与变量名绑定以方便后文调用。在检索出的结果中,56178a0d5f301baf6cf3e1cd53d9863437345bf9
是一个巨鲸,但其身份未被揭露,该地址持有大量资产,地址可前往 此页面 看到该地址的链上财务信息。而 28c6c06298d514db089934071355e5743bf21d60
是 Binance 的地址,从交易记录来看,此地址主要用于入账。
我们希望对 56178a0d5f301baf6cf3e1cd53d9863437345bf9
的间接提款交易进行追查,因为中介方可能包含更多信息。我们构造的查询如下:
1 | ?[transferMeida, meidaToken, meidaHash, transferHash] := |
最终获得如下输出:
transferMeida | meidaToken | meidaHash | transferHash |
---|---|---|---|
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | AAVE | 0x3fe283fc31c2746e4cb76609bd9c5bbcaeb40dc05c0573e4d6406acd8745199e | 0x99aa41dfa8847e6dfd9d487d464a81470a7daf426bb81445e595f7e2c5fccd50 |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | AAVE | 0x3fe283fc31c2746e4cb76609bd9c5bbcaeb40dc05c0573e4d6406acd8745199e | 0xe8b31a052cb39c4c983781c621d7529f19bef60d1cda6b825639e16925fead23 |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | RAD | 0x0da42a3520d73474325bb625c90c9349ea3cd19b2935381a4740502c024f0e6c | 0xa584e28537fa845d52f07df1302e5dc2f50c759fb049a52dbe2e24ceeb6c4dab |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x14fc352b81aa947785134e521fc4534e14b98806f4e2ea9e4f20256f71d4d967 | 0x02260768f2a62bc103c08455f1159d7d2a6eeab91201a08153f7d6c305b4931e |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x14fc352b81aa947785134e521fc4534e14b98806f4e2ea9e4f20256f71d4d967 | 0x0b269c40c0f1677521d8c8019e3455d33df83fb5c4e83e297d15fbe0323a74c0 |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x14fc352b81aa947785134e521fc4534e14b98806f4e2ea9e4f20256f71d4d967 | 0xcdda0e83cd76549dcba3fda75ae357a5fbca5ee0b9c9519bc34872216ff9c304 |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x30f487d24fb6767a10c8d520eeb8a8580180d25bdd2164e877ecc9d79d0d0074 | 0x02260768f2a62bc103c08455f1159d7d2a6eeab91201a08153f7d6c305b4931e |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x30f487d24fb6767a10c8d520eeb8a8580180d25bdd2164e877ecc9d79d0d0074 | 0x0b269c40c0f1677521d8c8019e3455d33df83fb5c4e83e297d15fbe0323a74c0 |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x30f487d24fb6767a10c8d520eeb8a8580180d25bdd2164e877ecc9d79d0d0074 | 0xcdda0e83cd76549dcba3fda75ae357a5fbca5ee0b9c9519bc34872216ff9c304 |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x3429d25645e66e9539aafd52b65b322a0ab02e0ee6dfc6caaad00af8f9026585 | 0x02260768f2a62bc103c08455f1159d7d2a6eeab91201a08153f7d6c305b4931e |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x3429d25645e66e9539aafd52b65b322a0ab02e0ee6dfc6caaad00af8f9026585 | 0x0b269c40c0f1677521d8c8019e3455d33df83fb5c4e83e297d15fbe0323a74c0 |
fa103c21ea2df71dfb92b0652f8b1d795e51cdef | USDC | 0x3429d25645e66e9539aafd52b65b322a0ab02e0ee6dfc6caaad00af8f9026585 | 0xcdda0e83cd76549dcba3fda75ae357a5fbca5ee0b9c9519bc34872216ff9c304 |
我们可以看到 fa103c21ea2df71dfb92b0652f8b1d795e51cdef
将许多从 Coinbase
中提取的资产转移给了 56178a0d5f301baf6cf3e1cd53d9863437345bf9
地址,这说明该地址与 56178a0d5f301baf6cf3e1cd53d9863437345bf9
的实控人可能是同一人。但我们仍不确认相关情况,我们需要检查 fa103c21ea2df71dfb92b0652f8b1d795e51cdef
的交易情况,如果该地址仅与 56178a0d5f301baf6cf3e1cd53d9863437345bf9
进行转账则说明该地址与上述地址时控人为同一人。因此,我们可以构造以下查询:
1 | ?[txTo, symbol] := *ERC20trans{txFrom, txTo, symbol}, txFrom="fa103c21ea2df71dfb92b0652f8b1d795e51cdef" |
获得如下检索结果:
txTo | symbol
——————————————–+——–
“56178a0d5f301baf6cf3e1cd53d9863437345bf9” | “AAVE”
“56178a0d5f301baf6cf3e1cd53d9863437345bf9” | “LDO”
“56178a0d5f301baf6cf3e1cd53d9863437345bf9” | “RAD”
“56178a0d5f301baf6cf3e1cd53d9863437345bf9” | “USDC”
“56178a0d5f301baf6cf3e1cd53d9863437345bf9” | “WETH”
我们基本可以确认 fa103c21ea2df71dfb92b0652f8b1d795e51cdef
和 56178a0d5f301baf6cf3e1cd53d9863437345bf9
为同一人的两个地址,其中 fa103c21ea2df71dfb92b0652f8b1d795e51cdef
作为资产暂存地址。
该部分内容事实上属于
OSINT
,即开源情报调查,读者可以通过 此篇文章 获得更多关于以太坊开源情报调查的工具
图算法
路径搜索
在本节中,我们主要讨论一些较为常见的图算法,包括路径查找算法和社群发现算法,cozodb
实现了大量的图算法,具体可以参考 文档 。但文档内缺少部分示例,所以本文会手把手介绍如何使用 cozodb
自带图算法。
关于图算法,读者可以阅读 《数据分析之图算法:基于Spark和Neo4j》,此书内大部分图算法均已被
cozodb
实现。如果读者希望更加深入的了解这一领域,可以关注 网络科学 这一学科主题,我认为 《巴拉巴西网络科学》 是一本很好的入门书
我们首先通过一个简单的示例讨论路径搜索算法的使用。在上文中,我们发现存在部分代币从 Coinbase 被转移到了 Binance 中,这种交易所转换行为是令人较为好奇的,我们希望获得所有从 Coinbase 转换到 Binance 的用户地址。这是一个简单的路径查询问题,我们已经确认起点是 Coinbase 的地址a9d1e08c7793af67e9d92fe308d5697fb81d3e43
,而终点是 Binance 的存款地址,我们只需要一个算法以 Coinbase 地址作为起点沿交易进行搜索直到 Binance 的地址即可。显然,这是一个寻路问题,如果读者熟悉一般的寻路算法,就会发现宽度优先搜索(BFS)和深度优先搜索(DFS)都可以完成此过程。
下图展示了两者搜索的基本过程:
在正式进行搜索前,我们需要获得 Binance 的存款地址。为方便后文讨论,我们仅假设用户只使用 Binance 14 进行存款操作。
事实上, Binance 应该拥有 42 个用于交互的地址。这些地址可以在此包含所有 Etherscan Tag 标签的JSON 文件 内获得。
我们首先通过文档发现以下定义:
1 | BreadthFirstSearch(edges[from, to], nodes[idx, ...], starting?[idx], condition: expr, limit: 1) |
此算法为 BFS 算法的定义,而 DFS 定义与之完全相同。该函数需要以下参数:
edges
由边构成的集合。
在此案例中即由txFrom
和txTo
转账关系。注意此处使用了[from, to]
说明要求输入表格的有且仅有两列且必须为from
和to
。此处涉及到[]
的提取问题,该操作符会按顺序提取数据,且要求列与[]
内的元素数量相同,具体参考 文档。简单来说,输入的
edges
进入函数后,函数会首先检查列数是否为 2 列,然后将第一列作为from
,而第二列作为to
。如果我们输入的表格为三列,则会直接报错。显然,我们的
ERC20trans
不符合要求,我们需要建立一张临时表,如下:1
transfer[from, to] := *ERC20trans{txFrom: from, txTo: to}
该表格仅存在两列,且符合要求。
nodes
由节点构成的集合
注意此处使用了[idx, ...]
限制,该限制要求nodes
表第一列是与edges
对应的标识符,在本案例中,idx
应为交易者地址。而...
表示nodes
表格可具有其他任意列,这些列可以用于condition
参数进行条件判断。我们可以使用以下代码构造
nodoes
:1
nodeAddress[address] := *ERC20trans{txFrom: address}, *ERC20trans{txTo: address};
cozodb
严格遵循集合语义,所以此处的检索会自动去重starting
开始进行搜索的节点
?
说明本参数可以置为空,但一旦置为空就会在整个图数据集内进行搜索,显然,这导致大量运算。而[idx]
参数也说明该表格只能存在一列,该列与edges
对应。在此案例中,我们构造如下检索即可:
1
coinBase[address] := nodeAddress[address], address="a9d1e08c7793af67e9d92fe308d5697fb81d3e43";
condition
中止搜索的条件。该条件应该为一个布尔表达式limit
产生结果的数量。默认为1
,即找到一个符合condition
的结果后立即停止搜索。
综上所述,我们最终构造的检索如下:
1 | transfer[from, to] := *ERC20trans{txFrom: from, txTo: to} |
搜索结果如下:
_0 | _1 | _2 |
---|---|---|
“a9d1e08c7793af67e9d92fe308d5697fb81d3e43” | “28c6c06298d514db089934071355e5743bf21d60” | [“a9d1e08c7793af67e9d92fe308d5697fb81d3e43”,”4d078ccdf62e287514adb078fdfddaddb1890067”,”28c6c06298d514db089934071355e5743bf21d60”] |
上述结果中,各列含义如下:
_0
搜索的起始节点_1
搜索的终点_2
搜索的路径
我们可以看到 4d078ccdf62e287514adb078fdfddaddb1890067
进行了交易所转换,我们可以查找一下该地址转移代币的详细情况:
1 | ?[txHash, txValue, symbol] := |
我们通过 or
连接多条筛选规则,实现了交易的提取。由于 cozodb
是大小写敏感的数据库,所以此处的 or
只能使用小写。在此处,我们也给出 cozodb
中各算符的优先级,即算符 and
的算符优先级比 or
高, or
又比逗号高,逗号在语义上与 and
一致,但优先级不同。
最后的搜索结果如下:
1 | txHash | txValue | symbol |
该用户提取了 USDC 并将其转账到 Binance 。
如果读者使用 DFS
算法进行搜索,则会得到一个极其离谱的代币转移路径,如下:
1 | ["a9d1e08c7793af67e9d92fe308d5697fb81d3e43","fa103c21ea2df71dfb92b0652f8b1d795e51cdef", |
简单查看了最后一笔和第一笔交易都是正确的,但中间过程没有进行详细研究,读者如果对此感兴趣可以自己研究此代币转移流程。
社群发现
可能有部分对于图算法不熟悉的用户不清楚 社群发现 的含义。社群发现指在大规模图数据库中找到一系列属于同一集团的节点。发现社团的一般性原则是,社团成员在群组内部的关系要多于其与群组外部节点的关系。理论上,我们可以使用社群发现算法识别属于同一实体的地址。值得注意的是,使用社群发现算法识别属于同一实体的节点是存在一些问题的。正如上文所述,社群发现的基本原则是 社团成员在群组内部的关系要多于其与群组外部节点的关系
,某些实体可以通过创建一系列钱包并在长时期内不进行实体内钱包交互实现来避免被社群发现算法发现。
社群发现算法主要包括以下几类:
- 三角形计数和聚类系数
- 强连通分量算法
- 连通分量算法
- 标签传播算法
- Louvain模块度算法
我们不会在本文内详细讨论这些算法的实现等,读者可以自行阅读 《数据分析之图算法:基于Spark和Neo4j》 查看算法的一些细节。
cozodb
对应的函数如下:
ClusteringCoefficients
三角形计数CommunityDetectionLouvain
Louvain模块度算法LabelPropagation
标签传播算法
在这些算法中,LabelPropagation
具有以下特点:
标签传播算法通常用于发现大规模网络中的初始社团,当权重可用时尤其适用。该算法可以并行化处理,因此在分割图时速度非常快。
在本案例中,我们主要使用此算法。
为方便后文判断社群发现的效果,我们导入 etherscan
标签数据集:
1 | res[address, name] <~ |
此处使用的 CSV 文件是我使用 Python 对 Etherscan CombinedLables JSON 文件清洗出的。基本格式如下:
1 | 0x0e8ba001a821f3ce0734763d008c9d7c957f5852,AmadeusRelay |
在文档中,LabelPropagation
定义如下:
1 | LabelPropagation(edges[from, to, weight?], undirected: false, max_iter: 10) |
在上文中,我们已经讨论了如何理解这些图算法定义,所以此处我们构造一个简单的查询(直接查询返回的行数过多,所以此处使用 limit
限制返回数):
1 | transfer[from, to, weight] := *ERC20trans{txFrom: from, txTo: to, txValue: weight}; |
但是此查询产生的结果较为简单,我们无法进行一些有效的分析,所以我们使用以下复杂检索来补充地址标签:
1 | transfer[from, to, weight] := *ERC20trans{txFrom: from, txTo: to, txValue: weight}; |
与上文的简单检索类似,此处最重要的是与 etherscanTags
表格进行了合并查询。由于 ERC20trans
中的地址没有 0x
前缀,而 etherscanTags
中的地址包含 0x
前缀,所以此处使用了 concat
拼接字符串。我们截取部分结果,如:
1 | 4356 | "0x000000000005af2ddc1a93a03e9b7014064d3b8d" | "MEV Bot: 0x00...b8D" |
可以发现标签传播算法成功发现了 MEV Bot
的社群关系,此处出现 Shiba Inu: ShibaSwap
的原因可能是 Shib
等 meme
币单次转账数额巨大,赋权过高导致的。读者可以通过调整权重(比如使用归一化的 txValue
) 等方法获得更好的社群发现。
当然,以下社群也是值得关注的:
1 | 3902 | "0x05767d9ef41dc40689678ffca0608878fb3de906" | "SushiSwap: CVX" |
该社区成功聚合了各大 DEX 尤其是 SushiSwap 的各个地址。但此处为什么会混入 Uniswap V3
的原因不明。读者可以考虑输出全社群地址集合(即包含未包含标签的集合)进行进一步研究。这正是社群发现算法的一大用途,将大规模图数据集进行分解以是我们可以聚集于子图中。
读者可以记得上文我们分析了 56178a0d5f301baf6cf3e1cd53d9863437345bf9
与 fa103c21ea2df71dfb92b0652f8b1d795e51cdef
为同一实控人,我们可以查看在两者是否被社群发现算法分为同一类,为保证后期查询,我们首先创建一个表格存储社群发现算法结果,如下:
1 | transfer[from, to, weight] := *ERC20trans{txFrom: from, txTo: to, txValue: weight}; |
:create
用于创建数据库,该命令直接使用上文 ?[groupId, address]
的查询结果。
运行以下算法查询结果:
1 | ?[groupId, address] := |
返回如下:
1 | groupId | address |
社群发现算法认为这两个地址属于同一实控人。我们可以查看 3048
群体内还有哪些地址:
1 | allAddress[groupId, suffixAddress, tag] := |
此处为了实现外连接(Outer join
) 的逻辑,我们使用了大量检索,接下来,我们会逐一进行分析。首先读者需要明确相对于 SQL
中的高度抽象化的布尔代数,cozoscript
的抽象程度较低。简单来说,我们键入的每一条命令都是一个抽象程度不高的布尔代数式,这些式子构成了一个过滤器对数据库进行过滤。所以此处如果实现外连接,我们必须知道外连接的布尔表达式,如下:
1 | (Table_A ∩ Table_B) + (Table_A - Table_B) |
设此处的 Table_A
为检索出的不带有标签的所有属于 groupId = 3048
的数据,我们将其命名为 allAddress
,而 Table B
为包含标签的所以属于 groupId = 3048
的数据,我们将其命名为 tagAddress
。关于这两个表的具体构造过程,上文代码已经给出了分析。使用上述定义替换布尔表达式内的变量,得到:
1 | (allAddress ∩ tagAddress) + (allAddress - tagAddress) |
我们发现 tagAddress
内的数据都在 allAddress
内出现过,即 tagAddress ⊂ allAddress
,那么上述布尔表达式可以改写为:
1 | allAddress ∩ tagAddress = tagAddress |
接下来,我们着重构造 allAddress - tagAddress
使用 not
操作符即可,即 not tagAddress[groupId, address, _]
,此处使用 _
表示 tag
列被忽略,不参与最终的代数运算。最后构造出的表格为 allMinusTagAddress
。
_
的含义简单来说就是该行可以在操作中被忽略。为什么我们在上文没有使用过此操作符?原因是上文使用的{}
表格可以进行任意列的提取,而此处使用的[]
表格必须给出每一个列绑定的变量名,具体请参考 文档。
最后实现 +
加法,使用 and
操作符即可。最终,我们构造出了上文给出的复杂查询。
此处应感谢 teradata 文档 中给出的外连接的关系代数。在该数据库文档内也包含了一些其他 SQL 检索的布尔代数表达式,如果读者有兴趣可以简单阅读。
返回值如下:
1 | groupId | address | tag |
读者可以尝试进行可视化来观察这些地址之间的关系,并进行进一步分析。
中心性算法
中心性算法主要勇于寻找图数据集中最重要的节点。涉及以下话题时,我们可能需要中心性算法:
- DeFi 项目热度分析
- 区块链中最重要的节点
下表展示了常见的中心性算法:
算法类型 | 作用 | 函数 |
---|---|---|
度中心性算法 | 度量节点拥有的关系数量 | DegreeCentrality |
接近中心性算法 | 计算哪些节点具有到其他各节点的最短路径 | ClosenessCentrality |
PageRank 算法 | 通过与当前节点相连的节点以及它们的邻节点来估计当前节点的重要性 | PageRank |
中间中心性算法 | 度量通过某个节点的最短路径的数量 | BetweennessCentrality |
中间中心性算法复杂度极高,请勿在大规模图数据集上使用,可以先使用上文的社群发现算法分割图数据集,然后使用此算法
也可以使用下图总结:
这些内容均来自 《数据分析之图算法:基于Spark和Neo4j》 第五章,详细内容请阅读此书
我们在此处仅展示 DegreeCentrality
的用法:
1 | transfer[from, to] := *ERC20trans{txFrom: from, txTo: to}; |
返回结果如下:
1 | address | allLink | toLink | fromLink |
读者可以自行探索其他中心性算法的用法和作用。
总结
本文简单介绍了如何使用 CosmosGraph
进行大规模图数据可视化和使用 cozodb
进行图数据分析的相关内容,介绍了如何使用 cozodb
进行图数据检索和运行图算法以获得需要的结果。
本文没有详细介绍图算法的相关内容,读者可自行参考 《数据分析之图算法:基于Spark和Neo4j》 和 《巴拉巴西网络科学》等书籍研究图算法的背景知识和数学原理。本文没有涉及到 cozodb
的数据备份问题,而全程使用了 mem
内存数据引擎,该数据引擎虽然可以快速进行图数据分析,但一旦关闭就意味着数据丢失,读者可以自行研究如何使用其他数据引擎实现数据备份。
本文尽可能使用通俗的语言解释了 cozoscript
的相关内容,相信对于读者理解 cozodb
的文档是有帮助的。cozodb
的文档中大量使用了专有名词,理解难度较高,希望此文可以降低读者使用 cozodb
的门槛。