【问题标题】:Find All Binary Splits of a Nominal Attribute查找名义属性的所有二元拆分
【发布时间】:2016-10-20 15:10:12
【问题描述】:

问题

我正在尝试基于仅具有名义属性的数据集在 Python 中从头开始构建二元决策树分类器。

我坚持的一个步骤是找到所有可能的方法来计算名义属性的二进制拆分。例如,对于具有可能值 [a, b, c, d] 的属性,我正在寻找一种方法将它们拆分为两个数组,以便我们获得:

  left     right
  ----     -----
  a        bcd
  b        acd
  c        abd
  d        abc
  ab       cd
  ac       bd
  ad       bc

没有重复拆分(例如,我们不需要left 中的“bc”和right 中的“ad”,因为这将产生与left 中的“ad”和@ 中的“bc”相同的二进制拆分987654326@)。每个拆分中的顺序也无关紧要(例如,“ad”与拆分一侧的“da”相同)。

当前尝试

确切的术语让我无法理解,但我认为这是某种形式的组合/排列问题。我知道它不是我所追求的强大力量。我能找到的与我相似的最接近的问题是链接here

到目前为止,我已经开始了一个迭代过程:

for i in range(1, array.length / 2):
  for j in range(1, i):
     # stuck here

之所以只循环遍历属性可能值(array)一半长度的下限是因为如果我们在left 中存储最多array.length / 2 值,则右边有1 - (array.length / 2) 值,涵盖所有可能的分裂。

另外,我听说过 itertools 库 .. 所以也许有办法实现我的目标?

【问题讨论】:

    标签: python split combinations permutation decision-tree


    【解决方案1】:

    仅供参考,您的二进制拆分也称为partitions,恰好包含 2 个部分。每个 2 分区完全由一个子集确定(分区的另一半​​是子集的补集),因此与组合的关系。

    事实上,如果您在shortlex ordergenerate the powerset 您的字符串,您基本上可以将powerset 对折以产生所需的分区。

    import itertools
    
    def bsplit(chars):
        "Returns a list of all unique 2-partitions."
        assert len(chars) >= 2
    
        # first, we generate the powerset in shortlex order,
        # minus the empty set and its complement
        subsets = (itertools.combinations(chars, k) for k in range(1, len(chars)))
        subsets = itertools.chain.from_iterable(subsets)
        subsets = [''.join(sub) for sub in subsets]
    
        # then, we "fold" the set in half--pairing each subset 
        # in the first half with its complement from the second half
        half = len(subsets) // 2
        return list(zip(subsets[:half], reversed(subsets[half:])))
    
    def test(*strings):
        for string in strings:
            for left, right in bsplit(string):
                print(left, right)
            print()
    
    test('ab', 'abc', 'abcd', 'abcde')
    

    这也表明对于一组大小为n(2^n - 2) / 2) = 2^(n - 1) - 1) 分区。

    显然,您不能将其用于大型序列,因为它需要一次实现(几乎)整个 powerset。虽然,它确实提出了一种避免生成重复项的有效解决方案:枚举有序幂集的第一个 2^(n - 1) - 1) 非空元素,并将每个子集映射到其对应的分区。

    【讨论】:

      【解决方案2】:

      我会使用itertools.product 编写一个函数,将一个序列拆分为所有可能的两半部分。我会遍历它并使用集合删除重复项。

      import itertools
      
      def binary_splits(seq):
          for result_indices in itertools.product((0,1), repeat=len(seq)):
              result = ([], [])
              for seq_index, result_index in enumerate(result_indices):
                  result[result_index].append(seq[seq_index])
              #skip results where one of the sides is empty
              if not result[0] or not result[1]: continue
              #convert from list to tuple so we can hash it later
              yield map(tuple, result)
      
      def binary_splits_no_dupes(seq):
          seen = set()
          for item in binary_splits(seq):
              key = tuple(sorted(item))
              if key in seen: continue
              yield key
              seen.add(key)
      
      for left, right in binary_splits_no_dupes("abcd"):
          print left, right
      

      结果:

      ('a', 'b', 'c') ('d',)
      ('a', 'b', 'd') ('c',)
      ('a', 'b') ('c', 'd')
      ('a', 'c', 'd') ('b',)
      ('a', 'c') ('b', 'd')
      ('a', 'd') ('b', 'c')
      ('a',) ('b', 'c', 'd')
      

      【讨论】:

      • 你能解释一下itertools.product((0,1), repeat=len(seq))在做什么吗?如果我错了,请纠正我:从外观上看,提供了 (0,1),因为这是一个二进制拆分,但我不太确定 repeat=len(seq)。只要我们了解 itertools.product 函数返回的值代表什么,这段代码的其余部分就相对简单了。
      • result_indices是一个0和1的元组,和seq的长度相同。每个 0 表示“seq 在该位置的值应该在左列”,每个 1 表示“seq 在这个位置的值应该在右列”。示例: (0,1,0) 的 result_indices 将“ABC”分隔为 (("A","C"), ("B",))。 (1,0,0) 的result_indices 将“ABC”分隔为 (("B", "C"), ("A"))。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多