为数百万个术语建立多对多关系

5 投票
2 回答
863 浏览
提问于 2025-04-18 14:37

我在一个包含大约400万条记录的Python sqlite3数据库中有以下信息。

Term         No of articles      Article Ids
Obama           300            [411,523,534, …. 846]
Gandhi         3900            [23,32,33…..4578]
Mandela        3900            [21,14,56,145 …4536]
George Bush     450            [230,310 … 700]
Tony Blair      350            [225,320 … 800]
Justin Bieber   25             [401 , 420, 690 …. 904]
Lionel Messi    150            [23, 78, …… 570]

这里的'Article Ids'是一个二进制大对象,里面存储着一系列的ID,这些ID是通过一个API返回的。

我的任务是从每个术语的ID列表中找出共同的ID,并把它们保存到'relationships.db'中。

我该如何建立关系,以找出哪些文章同时提到了甘地和曼德拉(也就是找出交集的文章ID)呢?

最终的relationships.db应该是这样的;

Term 1              Term 2          No of Common Article Ids    Common Article IDS
Obama               Gandhi                17                    [34,123,25 ...]
Obama               Mandela               43                    [145,111,234,456 ....]
Obama               George Bush           46
Obama               Tony Blair             2
Obama               Justin Bieber         36
Obama               Lionel Messi           3
Gandhi              Mandela               40
Gandhi              George Bush           41
Gandhi              Tony Blair            32
Gandhi              Justin Bieber         31
Gandhi              Lionel Messi          20
Mandela             George Bush           20
Mandela             Tony Blair            11
Mandela             Justin Bieber         19
Mandela             Lionel Messi          39
George Bush         Tony Blair            46
George Bush         Justin Bieber         49
George Bush         Lionel Messi           2
Tony Blair          Justin Bieber         50
Tony Blair          Lionel Messi           3
Justin Bieber       Lionel Messi           6

用'for循环'逐个遍历每个术语来获取交集实在是太麻烦了。有没有更高效的方法?这样做会在'内存'和'速度'之间有权衡吗?

2 个回答

1

你可以用pandas找到一些解决方法。

1) 首先,用pandas.read_sql创建一个pandas数据框(DataFrame)。

2) 然后,你可以像@logc建议的那样,获取一个交叉连接。

3) 接下来,你可以把列表转换成集合,并进行交集操作。

如果你需要帮助实现这些,我稍后可以帮你,现在有点急。

编辑:

好的,其实这很简单,但我不知道它是否能满足你的性能需求,可能你需要一步一步读取csv文件:

import pandas, sqlite3

conn = sqlite3.connect(databaseFilePath)
df=pandas.read_sql('SELECT * FROM Terms;',conn)
df['Article Ids'] = df['Article Ids'].apply(eval).apply(set)
df['key'] = False
df2 = pandas.merge(df,df,on='key')
df2 = df2[df2.Term_x!=df2.Term_y]
df2['Common Articles IDS'] = df2.apply(lambda row:set.intersection(row['Article Ids_x'], row['Article Ids_y']), axis=1)
df2['No of Common Articles Ids'] = df2['Common Articles IDS'].apply(len)
df2['Common Articles IDS'] = df2['Common Articles IDS'].apply(list).apply(str)
df2[['Term_x','Term_y', 'No of Common Articles Ids', 'Common Articles IDS']].to_sql(outputTableName, conn) 
2

评论中提到过这一点,但在信息还处于BLOB格式时,处理起来几乎是不可能的(或者说非常困难)。如果你重新设计一下数据库,可能会更有帮助:

Term      Article_id
Ghandi    33
Obama     411
Obama     523
Ghandi    23
Obama     846
...
Mandela   23

这种“扁平化”的表示方式有几个好处。首先,添加新文章变得简单,你不需要提取BLOB。其次,配合合适的索引,你可以轻松恢复原来的计数:

SELECT COUNT(*) FROM news WHERE Term="Ghandi"`

如果你愿意,这些数据可以存放在一个单独的表里。要提取联合文章,你可以搜索类似这样的内容:

SELECT A.Article_id, A.Term, B.Term FROM news AS A 
JOIN news AS B ON A.Article_id = B.Article_id 
AND A.Term != B.Term

需要注意的是,这样会导致重复计数,但这很容易纠正。下面是一个完整的最小工作示例,修改了表格以显示更多匹配项:

import sqlite3

conn = sqlite3.connect(":memory:")

raw_items = '''
Ghandi    33
Obama     411
Obama     521
Ghandi    23
Obama     21
Ghandi    411
Mandela   21'''

script = '''
CREATE TABLE news ( 
Term STRING,
Article_id INTEGER
);'''

conn.executescript(script)
items = [line.split() for line in raw_items.strip().split('\n')]
conn.executemany("INSERT INTO news VALUES (?,?)", items)

cmd = '''SELECT COUNT(*) FROM news WHERE Term="Obama"'''
print "Obama size: ", conn.execute(cmd).fetchone()

cmd = '''
SELECT A.Article_id, A.Term, B.Term FROM news AS A 
JOIN news AS B ON A.Article_id = B.Article_id 
AND A.Term != B.Term '''

for result in conn.execute(cmd).fetchall():
    print result

这样可以得到:

Obama size:  (3,)
(411, u'Obama', u'Ghandi')
(21, u'Obama', u'Mandela')
(411, u'Ghandi', u'Obama')
(21, u'Mandela', u'Obama')

撰写回答