通过执行本机极坐标命令而不是 UDF 函数来优化相似度分数的计算

Nik*_*kSp 5 python user-defined-functions python-polars

免责声明 (1):这个问题支持这个 SO。在两位用户请求详细说明我的案例后。

免责声明 (2) - 29/11 添加:到目前为止,我已经看到了两种利用该功能的解决方案(在此 SO 中提出的以及支持性的解决方案)explode()。根据我对整个数据集(约 300 万行数据)所做的一些基准测试,RAM 确实会爆炸explode(),因此我将在数据集的样本上测试该函数,如果它有效,我将接受那些可能在较小的数据集上进行实验的人的方法解决方案表。

输入数据集(约 300 万行)来自80_000 个 IMDb 电影的 ml-latest 数据集以及 330_000 个用户的相应评分(您可以从此处ratings.csv下载 CSV 文件- 891mb)。

polars我使用like加载数据集movie_ratings = pl.read_csv(os.path.join(application_path + data_directory, "ratings.csv"))application_path并且data_directory是本地服务器上的父路径。

阅读数据集后,我的目标是生成用户在所有其他用户之间的余弦相似度。为此,首先我必须将评级表(约 300 万行)转换为每个用户 1 行的表。因此,我运行以下查询

## 1st computation bottleneck using UDF functions (2.5minutes for 250_000 rows)
users_metadata = movie_ratings.filter(
        (pl.col("userId") != input_id) #input_id is a random userId. I prefer to make my tests using userId '1' so input_id=1 in this case.
    ).group_by("userId")\
        .agg(
            pl.col("movieId").unique().alias("user_movies"),
            pl.col("rating").alias("user_ratings")
        )\
        .with_columns(
            pl.col("user_movies").map_elements(
                lambda row: sorted( list(set(row).intersection(set(user_rated_movies))) ), return_dtype=pl.List(pl.Int64)
            ).alias("common_movies")
        )\
        .with_columns(
            pl.col("common_movies").map_elements(
                lambda row: len(row), return_dtype=pl.Int64
            ).alias("common_movies_frequency")
        )
similar_users = (
    users_metadata.filter(
       (pl.col("common_movies_frequency").le(len(user_rated_movies))) &
       (pl.col("common_movies_frequency").gt(0)) # we don't want the users that don't have seen any movies from the ones seen/rated by the target user.
    )
    .sort("common_movies_frequency", descending=True)
)

## 2nd computation bottleneck using UDF functions
similar_users = (
    similar_users.with_columns(
        pl.struct(pl.all()).map_elements(
            get_common_movie_ratings, #asked on StackOverflow
            return_dtype=pl.List(pl.Float64),
            strategy="threading"
        ).alias("common_movie_ratings")
    ).with_columns(
        pl.struct(["common_movies"]).map_elements(
            lambda row: get_target_movie_ratings(row, user_rated_movies, user_ratings),
            return_dtype=pl.List(pl.Float64),
            strategy="threading"
        ).alias("target_user_common_movie_ratings")
    ).with_columns(
        pl.struct(["common_movie_ratings","target_user_common_movie_ratings"]).map_elements(
             lambda row: compute_cosine(row),
             return_dtype=pl.Float64,
             strategy="threading"
        ).alias("similarity_score")
    )
)
Run Code Online (Sandbox Code Playgroud)

上面的代码片段按 userId 对表进行分组,并计算有关它们的一些重要元数据。具体来说,

  • user_movies、每个用户的 user_ ratings

  • common_movies = 用户观看的电影与 input_id 用户(因此用户 1)观看的电影相同的交集。用户1看过的电影基本上是user_rated_movies = movie_ratings.filter(pl.col("userId") == input_id).select("movieId").to_numpy().ravel()

  • common_movies_Frequency = 每个用户的列长度common_movies。每个用户的长度不是固定的。

  • common_movie_ ratings = 我在这里询问的函数的结果

  • target_user_common_movie_ ratings = 与每个用户的常见电影索引相匹配的目标用户(user1)的评分。

  • 相似度得分 = 余弦相似度得分。

表的屏幕截图(不要关注列potential recommendations在此输入图像描述

最后,我根据用户 1 看过的users_metadata62 ( ) 部电影中 common_movies_freq 小于或等于的所有用户来过滤该表。len(user_rated_movies)总共有 250_000 个用户。

该表是我在这个问题中询问的 UDF 函数的输入数据框。使用这个数据框(~250_000 个用户),我想计算每个用户与用户 1 的余弦相似度。为此,我想比较他们的评分相似度。因此,对于每个用户通常评分的电影,计算两个评分数组之间的余弦相似度。

以下是我用来支持我的功能的三个 UDF 函数。

def get_common_movie_ratings(row) -> pl.List(pl.Float64):
    common_movies = row['common_movies']
    user_ratings = row['user_ratings']
    ratings_for_common_movies = [user_ratings[list(row['user_movies']).index(movie)] for movie in common_movies]
    return ratings_for_common_movies

def get_target_movie_ratings(row, target_user_movies:np.ndarray, target_user_ratings:np.ndarray) -> pl.List(pl.Float64):
    common_movies = row['common_movies']
    target_user_common_ratings = [target_user_ratings[list(target_user_movies).index(movie)] for movie in common_movies]
    return target_user_common_ratings

def compute_cosine(row)->pl.Float64:
    array1 = row["common_movie_ratings"]
    array2 = row["target_user_common_movie_ratings"]
    magnitude1 = norm(array1)
    magnitude2 = norm(array2)
    if magnitude1 != 0 or magnitude2 != 0: #avoid division with 0 norms/magnitudes
        score: float = np.dot(array1, array2) / (norm(array1) * norm(array2))
    else:
        score: float = 0.0
    return score
Run Code Online (Sandbox Code Playgroud)

基准测试

  • 1 个用户的总执行时间约为 4 分钟。如果我必须通过每个用户的迭代(每个用户 1 个数据帧)来计算,则大约需要 4 分钟 * 330_000 个用户。
  • 为 1 位用户计算极坐标 df 时需要 3-5Gb RAM。

主要问题是如何将这 3 个 UDF 函数转换为原生的 Polars 命令。

来自我制作的自定义记录器的日志

2023-11-29 13:40:24 - INFO - 计算 254188 个用户的潜在相似用户元数据:0:02:15.586497

2023-11-29 13:40:51 - INFO - 计算 194943 个用户的相似度分数:0:00:27.472388

我们可以得出结论,代码的主要瓶颈是在创建表时user_metadata

jqu*_*ous 4

CSV

\n
    \n
  • pl.read_csv将所有内容加载到内存中。

    \n
  • \n
  • pl.scan_csv()相反,返回一个 LazyFrame。

    \n
  • \n
\n

实木复合地板

\n
    \n
  • 读/写速度更快
  • \n
  • pl.scan_csv("imdb.csv").sink_parquet("imdb.parquet")
  • \n
  • imdb.csv = 891mb / imdb.parquet = 202mb
  • \n
\n

例子:

\n

为了使复制结果变得更简单,我过滤了数据集pl.col("userId").is_between(1, 3)并删除了该timestamp列:

\n
movie_ratings = pl.read_csv(\n    b\'userId,movieId,rating\\n1,1,4.0\\n1,110,4.0\\n1,158,4.0\\n1,260,4.5\\n1,356,5.0\\n1,381,3.5\\n1,596,4.0\\n1,1036,5.0\\n1,1049,\'\n    b\'3.0\\n1,1066,4.0\\n1,1196,3.5\\n1,1200,3.5\\n1,1210,4.5\\n1,1214,4.0\\n1,1291,5.0\\n1,1293,2.0\\n1,1376,3.0\\n1,1396,3.0\\n1,153\'\n    b\'7,4.0\\n1,1909,3.0\\n1,1959,4.0\\n1,1960,4.0\\n1,2028,5.0\\n1,2085,3.5\\n1,2116,4.0\\n1,2336,3.5\\n1,2571,2.5\\n1,2671,4.0\\n1,2\'\n    b\'762,5.0\\n1,2804,3.0\\n1,2908,4.0\\n1,3363,3.0\\n1,3578,5.0\\n1,4246,4.0\\n1,4306,4.0\\n1,4699,3.5\\n1,4886,5.0\\n1,4896,4.0\\n1\'\n    b\',4993,4.0\\n1,4995,5.0\\n1,5952,4.5\\n1,6539,4.0\\n1,7064,3.5\\n1,7122,4.0\\n1,7139,3.0\\n1,7153,5.0\\n1,7162,4.0\\n1,7366,3.5\'\n    b\'\\n1,7706,3.5\\n1,8132,5.0\\n1,8533,5.0\\n1,8644,3.5\\n1,8961,4.5\\n1,8969,4.0\\n1,8981,3.5\\n1,33166,5.0\\n1,33794,3.0\\n1,40629\'\n    b\',4.5\\n1,49647,5.0\\n1,52458,5.0\\n1,53996,5.0\\n1,54259,4.0\\n2,1,5.0\\n2,2,3.0\\n2,6,4.0\\n2,10,3.0\\n2,11,3.0\\n2,17,5.0\\n2,1\'\n    b\'9,3.0\\n2,21,5.0\\n2,25,3.0\\n2,31,3.0\\n2,34,5.0\\n2,36,5.0\\n2,39,3.0\\n2,47,5.0\\n2,48,2.0\\n2,50,4.0\\n2,52,3.0\\n2,58,3.0\\n2\'\n    b\',95,2.0\\n2,110,5.0\\n2,111,3.0\\n2,141,5.0\\n2,150,5.0\\n2,151,5.0\\n2,153,3.0\\n2,158,3.0\\n2,160,1.0\\n2,161,3.0\\n2,165,4.0\'\n    b\'\\n2,168,3.0\\n2,172,2.0\\n2,173,2.0\\n2,185,3.0\\n2,186,3.0\\n2,204,3.0\\n2,208,3.0\\n2,224,3.0\\n2,225,3.0\\n2,231,4.0\\n2,235,3\'\n    b\'.0\\n2,236,2.0\\n2,252,3.0\\n2,253,2.0\\n2,256,3.0\\n2,261,4.0\\n2,265,2.0\\n2,266,4.0\\n2,282,1.0\\n2,288,1.0\\n2,292,3.0\\n2,29\'\n    b\'3,3.0\\n2,296,5.0\\n2,300,4.0\\n2,315,3.0\\n2,317,3.0\\n2,318,5.0\\n2,333,3.0\\n2,337,3.0\\n2,339,5.0\\n2,344,3.0\\n2,349,4.0\\n2\'\n    b\',350,3.0\\n2,356,5.0\\n2,357,5.0\\n2,364,4.0\\n2,367,4.0\\n2,377,4.0\\n2,380,4.0\\n2,420,2.0\\n2,432,3.0\\n2,434,4.0\\n2,440,3.0\'\n    b\'\\n2,442,3.0\\n2,454,3.0\\n2,457,5.0\\n2,480,3.0\\n2,500,4.0\\n2,509,3.0\\n2,527,5.0\\n2,539,5.0\\n2,553,3.0\\n2,586,4.0\\n2,587,\'\n    b\'4.0\\n2,588,4.0\\n2,589,4.0\\n2,590,5.0\\n2,592,3.0\\n2,593,5.0\\n2,595,4.0\\n2,597,5.0\\n2,786,4.0\\n3,296,5.0\\n3,318,5.0\\n3,8\'\n    b\'58,5.0\\n3,2959,5.0\\n3,3114,5.0\\n3,3751,5.0\\n3,4886,5.0\\n3,6377,5.0\\n3,8961,5.0\\n3,60069,5.0\\n3,68954,5.0\\n3,69844,5.0\'\n    b\'\\n3,74458,5.0\\n3,76093,5.0\\n3,79132,5.0\\n3,81834,5.0\\n3,88125,5.0\\n3,99114,5.0\\n3,109487,5.0\\n3,112556,5.0\\n3,115617,5.\'\n    b\'0\\n3,115713,4.0\\n3,116797,5.0\\n3,119145,5.0\\n3,134853,5.0\\n3,152081,5.0\\n3,176101,5.0\\n3,177765,5.0\\n3,185029,5.0\\n3,1\'\n    b\'87593,3.0\\n\'\n)\n
Run Code Online (Sandbox Code Playgroud)\n

我们将假设input_id == 1

\n

收集所有所需信息的一种可能方法:

\n
# Finding the intersection first seems to use ~35% less RAM\n# than the previous join / anti-join approach\nintersection = (\n   movie_ratings\n    .filter(\n       (pl.col("userId") == 1)\n       | \n       ((pl.col("userId") != 1) &\n        (pl.col("movieId").is_in(pl.col("movieId").filter(pl.col("userId") == 1))))\n    )\n)\n\n(intersection.filter(pl.col("userId") == 1)\n  .join(\n     intersection.filter(pl.col("userId") != 1),\n     on = "movieId"\n  )\n  .group_by(pl.col("userId_right").alias("other_user"))\n  .agg(\n     target_user = pl.first("userId"),\n     common_movies = "movieId",\n     common_movies_frequency = pl.count(),\n     target_user_ratings = "rating",\n     other_user_ratings = "rating_right",\n  )\n)\n
Run Code Online (Sandbox Code Playgroud)\n
shape: (2, 6)\n\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82 other_user \xe2\x94\x86 target_user \xe2\x94\x86 common_movies      \xe2\x94\x86 common_movies_frequency \xe2\x94\x86 target_user_ratings  \xe2\x94\x86 other_user_ratings   \xe2\x94\x82\n\xe2\x94\x82 ---        \xe2\x94\x86 ---         \xe2\x94\x86 ---                \xe2\x94\x86 ---                     \xe2\x94\x86 ---                  \xe2\x94\x86 ---                  \xe2\x94\x82\n\xe2\x94\x82 i64        \xe2\x94\x86 i64         \xe2\x94\x86 list[i64]          \xe2\x94\x86 u32                     \xe2\x94\x86 list[f64]            \xe2\x94\x86 list[f64]            \xe2\x94\x82\n\xe2\x95\x9e\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa1\n\xe2\x94\x82 3          \xe2\x94\x86 1           \xe2\x94\x86 [4886, 8961]       \xe2\x94\x86 2                       \xe2\x94\x86 [5.0, 4.5]           \xe2\x94\x86 [5.0, 5.0]           \xe2\x94\x82\n\xe2\x94\x82 2          \xe2\x94\x86 1           \xe2\x94\x86 [1, 110, 158, 356] \xe2\x94\x86 4                       \xe2\x94\x86 [4.0, 4.0, 4.0, 5.0] \xe2\x94\x86 [5.0, 5.0, 3.0, 5.0] \xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98\n
Run Code Online (Sandbox Code Playgroud)\n

惰性API

\n

可能有更好的策略来并行化工作,但基线尝试可以简单地循环每个用户 ID

\n
movie_ratings = pl.scan_parquet("imdb.parquet")\n\nuser_ids = movie_ratings.select(pl.col("userId").unique()).collect().to_series()\n\nfor user_id in user_ids:\n    result = (\n        movie_ratings\n            .filter(pl.col("userId") == user_id)\n            ...\n    )\n    print(result.collect())\n
Run Code Online (Sandbox Code Playgroud)\n

鸭数据库

\n

我很好奇,所以决定检查duckdb进行比较。

\n
import duckdb\n\nduckdb.sql("""\nwith \n   db as (from movie_ratings)\nfrom \n   db target, db other\nselect \n   target.userId        target_user,\n   other.userId         other_user,\n   list(other.movieId)  common_movies,\n   count(other.movieId) common_movies_frequency,\n   list(target.rating)  target_user_ratings,\n   list(other.rating)   other_user_ratings,\nwhere \n   target_user = 1 and other_user != 1 and target.movieId = other.movieId\ngroup by \n   target_user, other_user\n""").pl()\n
Run Code Online (Sandbox Code Playgroud)\n
shape: (2, 6)\n\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82 target_user \xe2\x94\x86 other_user \xe2\x94\x86 common_movies      \xe2\x94\x86 common_movies_frequency \xe2\x94\x86 target_user_ratings  \xe2\x94\x86 other_user_ratings   \xe2\x94\x82\n\xe2\x94\x82 ---         \xe2\x94\x86 ---        \xe2\x94\x86 ---                \xe2\x94\x86 ---                     \xe2\x94\x86 ---                  \xe2\x94\x86 ---                  \xe2\x94\x82\n\xe2\x94\x82 i64         \xe2\x94\x86 i64        \xe2\x94\x86 list[i64]          \xe2\x94\x86 i64                     \xe2\x94\x86 list[f64]            \xe2\x94\x86 list[f64]            \xe2\x94\x82\n\xe2\x95\x9e\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa1\n\xe2\x94\x82 1           \xe2\x94\x86 3          \xe2\x94\x86 [4886, 8961]       \xe2\x94\x86 2                       \xe2\x94\x86 [5.0, 4.5]           \xe2\x94\x86 [5.0, 5.0]           \xe2\x94\x82\n\xe2\x94\x82 1           \xe2\x94\x86 2          \xe2\x94\x86 [1, 110, 356, 158] \xe2\x94\x86 4                       \xe2\x94\x86 [4.0, 4.0, 5.0, 4.0] \xe2\x94\x86 [5.0, 5.0, 5.0, 3.0] \xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xb4\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98\n
Run Code Online (Sandbox Code Playgroud)\n

内存使用情况

\n

针对完整数据集运行这两个示例(运行时间基本相同)我得到:

\n
import rich.filesize\n\nprint("duckdb:", rich.filesize.decimal(223232000))\nprint("polars:", rich.filesize.decimal(1772072960))\n
Run Code Online (Sandbox Code Playgroud)\n
duckdb: 223.2 MB\npolars: 1.8 GB\n
Run Code Online (Sandbox Code Playgroud)\n

所以看来Polars方面还有潜在的改进空间。

\n