【问题标题】:Sort a column so that one column follows the values from another column对列进行排序,以便一列跟随另一列的值
【发布时间】:2020-02-03 16:46:14
【问题描述】:

我有一个代表公司层次结构树的大型数据集。举个例子,我可能有如下内容:

Child                 Parent
273500                273500
20574624              273500
2202652               1879450
19933526              1879450
18000796              18352628
18352628              19770000
1359996               20574624
1879450               20574624
18441258              20574624
20637582              20574624
20840426              20574624
20844632              20574624
20934910              20574624
20965442              20574624
21193122              20574624
21194666              21193122
19770000              20574624
19681810              18352628
19931554              20574624
18382902              1879450
19780666              1879450
20631784              20574624

如您所见,第一行是父节点。

我想做的是对数据进行排序,使其实际上代表一个层次结构,从顶部开始到层次结构的底部。我想这样做的原因是,因为我想计算树的高度。为此,首先我需要构建树。我已经知道如何使用treelib package 构建树。我现在的问题是,如果我有一个包含数千行的大型数据集,我如何以能够构建树的方式对数据进行排序。

我已经尝试过使用 pandas 中的 .sort_values 按子列中的值对父列进行排序。然而,这并没有按照我想要的方式工作。我还尝试通过函数分组来执行此操作,并根据以下问题以某种方式为行赋予一定的等级:pandas sort a column by values in another column。

这不适用于大型数据集。

下面是我想要得到的结果。

Child         Parent
273500        273500   # The first row is the parent row
20574624      273500   # I want all children that belong to this parent node
1879450       20574624 #  
18441258      20574624
19770000      20574624
19931554      20574624
20631784      20574624
20637582      20574624
20840426      20574624
20844632      20574624
20934910      20574624
20965442      20574624
21193122      20574624
2202652       1879450 # Now, I want all the children that belong to 1879450
18382902      1879450 # and so on
19780666      1879450
19933526      1879450
18352628      19770000
18000796      18352628
19681810      18352628
1359996       20574624
21194666      21193122

对于这么小的数据集,可以很容易地手动订购。但是对于包含数千行的大型数据集,这可能有点麻烦。

【问题讨论】:

  • df.sort_values(["Parent", "Child"])?
  • @politicalscientist:你的评论比我快 4 秒 :)。我删除了我的
  • 在这个具体的例子中,这个解决方案可以工作,但一般来说,如果有一个更大的数据集,其中数字不是从树级别到树级别的升序,那么它就行不通.我会想一个更大的例子来说明这一点。不幸的是,我无法上传更大的数据集。

标签: python pandas sorting


【解决方案1】:

定义如下函数:

def getDescendants(curr, par, level):
    res = [[curr, par, level]]
    children = df.query('Parent == @curr')
    for n in children.Child:
        if n != par:
            deeper = getDescendants(n, curr, level + 1)
            if len(deeper) > 0:
                res.extend(deeper)
    return res

然后获取“所有父母的父母”的id(从第0行开始):

hd = df.iloc[0, 0]

并调用上述函数:

pd.DataFrame(getDescendants(hd, hd, 1), columns=['Child', 'Parent', 'Level'])

这个函数做得更多。它还给出了每个人的水平 层次结构。

查找“所有父母的父母”的替代方法

如果“所有父母的父母”可能位于任何行(不一定 在第一个)中,需要另一种方法。

假设源DataFrame包含一个单个层次树, 根节点可以读作:hd = df.query('Parent == Child').iloc[0,0]。 然后如上生成层次树。

如果有多个层次树,那么:

  • df.query('Parent == Child').iloc[0] 获得 Series 的“根”ID。
  • 您必须为此系列中的每个元素编写一个循环,调用 getDescendants(id, id, 1) 获取当前id 并收集结果 (例如,作为列表的元素)。
  • 连接它们(垂直)。

【讨论】:

  • 这确实很好用。唯一的缺点可能是您必须提供“所有父母的父母”的输入。如果它不会在第 0 行 f.e. 中怎么办?只是一件小事。总的来说,它工作得非常好。非常感谢。欣赏它。
  • iloc[0,0] 在“hd = df.query('Parent == Child').iloc[0,0]”中有什么作用?提前致谢。
  • 请注意,df.query('Parent == Child') 为层次结构的每个“根”选择行。所以 iloc[0,0] 获取初始行(来自这些行)和来自这一行 - 初始列(person Id)。
  • 我改变了答案的最后一部分(一点点),希望能回答你的问题。
【解决方案2】:

我将在原版 Python 中执行此操作,而不是使用 pandas。基本上你要做的是构建一组树,然后从这些树的根节点开始遍历它们。

假设您已经解析了数据,您可以从结构List[Tuple[int, int]] 的一些列表processes 开始。

processes = [
    (273500, 273500),
    (20574624, 273500),
    (2202652, 1879450),
    (19933526, 1879450),
    (18000796, 18352628),
    (18352628, 19770000),
    (1359996, 20574624),
    (1879450, 20574624),
    (18441258, 20574624),
    (20637582, 20574624),
    (20840426, 20574624),
    (20844632, 20574624),
    (20934910, 20574624),
    (20965442, 20574624),
    (21193122, 20574624),
    (21194666, 21193122),
    (19770000, 20574624),
    (19681810, 18352628),
    (19931554, 20574624),
    (18382902, 1879450),
    (19780666, 1879450),
    (20631784, 20574624),
]

我们可以将树中的所有节点表示为父子关系的Dict[int, List[int]]。通过调用sort_processes(df.values.tolist()),可以在框架上调用以下方法。结果可以通过调用pandas.DataFrame(result, columns=['Child', 'Parent'])转换回pandas:

from collections import defaultdict
from typing import Dict, List, Iterable, Tuple

def sort_processes(processes: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
    # initialize the nodes
    nodes: Dict[int, List[int]] = defaultdict(list)
    for child, parent in processes:
        nodes[parent].append(child)

    # walk and yield pairs
    def walk_tree(parent: int) -> Iterable[Tuple[int, int]]:
        for child in sorted(nodes[parent]):
            yield (child, parent)
            # avoid infinite loops
            if parent != child:
                yield from walk_tree(child)

    # start at top level parents
    parents = [parent for child, parent in processes if parent == child]
    return list(
        pair for parent in sorted(parents) for pair in walk_tree(parent)
    )

调用sort_processes(processes) 返回:

[
    (273500, 273500),
    (20574624, 273500),
    (1359996, 20574624),
    (1879450, 20574624),
    (2202652, 1879450),
    (18382902, 1879450),
    (19780666, 1879450),
    (19933526, 1879450),
    (18441258, 20574624),
    (19770000, 20574624),
    (18352628, 19770000),
    (18000796, 18352628),
    (19681810, 18352628),
    (19931554, 20574624),
    (20631784, 20574624),
    (20637582, 20574624),
    (20840426, 20574624),
    (20844632, 20574624),
    (20934910, 20574624),
    (20965442, 20574624),
    (21193122, 20574624),
    (21194666, 21193122),
]

【讨论】:

  • 我对香草蟒不是很熟悉。只是为了让我开始,在我的具体情况下,我应该从 processes = List[ Tuple[ df.['Child'] , df.['Parent'] ] 开始还有这条线做什么: nodes: Dict[int, List[int]] = defaultdict(list)
  • 每个元组都是数据框中的一行。应该只是df.values.tolist()
  • @Eren defaultdict 是一个字典,如果您查询一个尚不存在的键,那么它将为您创建一个新值并返回它。在这种情况下,我们使用一个空的list 作为返回值。 : Dict[int, List[int]] 是一个类型提示。类型提示纯粹是为了允许像 mypy 这样的工具对你的代码进行类型检查以避免错误。使用它们是最佳做法,但它们完全是可选的,不会对功能产生影响。
【解决方案3】:

如果我理解正确你想要的是topological sort,我建议你使用networkx中实现的那个:

edges = df[df.child != df.parent].reset_index()
dg = nx.from_pandas_edgelist(edges, source='parent', target='child', create_using=nx.DiGraph)
order = list(nx.lexicographical_topological_sort(dg))

result = df.set_index('parent').loc[order, :].dropna().reset_index()
print(result)

输出

      parent       child
0     273500    273500.0
1     273500  20574624.0
2   20574624   1359996.0
3   20574624   1879450.0
4   20574624  18441258.0
5   20574624  20637582.0
6   20574624  20840426.0
7   20574624  20844632.0
8   20574624  20934910.0
9   20574624  20965442.0
10  20574624  21193122.0
11  20574624  19770000.0
12  20574624  19931554.0
13  20574624  20631784.0
14   1879450   2202652.0
15   1879450  19933526.0
16   1879450  18382902.0
17   1879450  19780666.0
18  19770000  18352628.0
19  18352628  18000796.0
20  18352628  19681810.0
21  21193122  21194666.0

如果您想保持列的顺序 (['child', 'parent']) 只需这样做:

result = df.set_index('parent').loc[order, :].dropna().reset_index().reindex(['child', 'parent'], axis=1)

一定要导入所需的库:

import networkx as nx
import pandas as pd

【讨论】:

  • 我收到“输入图不是 networkx 图类型”错误。你能告诉我除了networkx我还需要导入哪些包吗?
  • 我需要把 "create_using=nx.DiGraph()" 的括号放在 DiGraph 之后。
  • 只是好奇,您是否有理由不接受此答案为正确答案? @艾伦
  • 老实说,所有答案都是正确的。我可以全部接受吗?没有特别的理由不接受这个。 @尔凡
猜你喜欢
  • 2020-06-12
  • 2018-07-04
  • 1970-01-01
  • 1970-01-01
  • 2016-03-24
  • 2019-04-02
  • 1970-01-01
  • 2017-04-01
  • 2012-10-09
相关资源
最近更新 更多