使用Deedle按列去重并保留每个重复键的首次出现行
Deedle数据框按列去重保留首次出现行
问题场景
存在重复键值的数据表,需要删除重复键对应的冗余行,仅保留每个重复键首次出现的第一行。
初始读取数据代码:
let data = "A;B\na;1\nb;\nb;2\nc;3" let bytes = System.Text.Encoding.UTF8.GetBytes data let stream = new MemoryStream( bytes ) let df= Frame.ReadCsv( stream = stream, separators = ";", hasHeaders = true ) df.Print()
原始数据输出:
A B 0 -> a 1 1 -> b <missing> 2 -> b 2 3 -> c 3
期望输出:
A B 0 -> a 1 1 -> b <missing> 2 -> c 3
之前使用applyLevel的实现得到了错误结果,重复键b保留了第二行而非首次出现的首行:
let df1 = df |> Frame.groupRowsByString "A" |> Frame.applyLevel fst (fun s -> s |> Series.firstValue) df1.Print()
错误输出:
A B a -> a 1 b -> b 2 <- wrong c -> c 3
错误原因
Series.firstValue取的是序列按键排序后的第一个值,而非数据原始加载顺序的第一个值。分组后缺失值<missing>的排序优先级低于正常值,因此b分组中会优先取到B=2的行,不符合需求。
正确实现
直接按原始行顺序遍历去重列,记录已经出现过的键,保留每个键第一次出现的行索引,最后按索引筛选数据框即可,全程保留原始数据顺序,不需要额外排序:
let dfDedup = // 遍历去重键列,标记每个值是否首次出现 df.GetColumn<string>("A") |> Series.scanValues (fun (seen, _) key -> if Set.contains key seen then (seen, None) else (Set.add key seen, Some key) ) (Set.empty<string>, None) // 提取所有首次出现的行对应的原始索引 |> Series.values |> Seq.choose snd |> Seq.zip df.RowKeys |> Seq.map fst // 按索引筛选行,重置为连续序号索引 |> fun keepRows -> df.Rows.[keepRows] |> Frame.indexRowsOrdinally dfDedup.Print()
运行后输出完全符合预期:
A B 0 -> a 1 1 -> b <missing> 2 -> c 3
如果不需要重置行索引,去掉最后一行|> Frame.indexRowsOrdinally即可,此时会保留原始行号0、1、3。
内容的提问来源于stack exchange,提问作者jim108dev
相关产品推荐
相关产品推荐

