【问题标题】:Build a basic cube with numpy?用 numpy 构建一个基本的立方体?
【发布时间】:2021-02-26 11:09:45
【问题描述】:

我想知道numpy 是否可用于构建最基本的立方体模型,其中存储所有交叉组合及其计算值。

我们以下面的数据为例:

AUTHOR         BOOK          YEAR        SALES
Shakespeare    Hamlet        2000        104.2
Shakespeare    Hamlet        2001        99.0
Shakespeare    Romeo         2000        27.0
Shakespeare    Romeo         2001        19.0
Dante          Inferno       2000        11.6
Dante          Inferno       2001        12.6

并且能够构建类似的东西:

                             YEAR                  TOTAL
AUTHOR            BOOK       2000       2001         
(ALL)             (ALL)      142.8      130.6      273.4
Shakespeare       (ALL)      131.2      118.0      249.2
Dante             (ALL)      11.6       12.6       24.2
Shakespeare       Hamlet     104.2      99.0       203.2
Shakespeare       Romeo      27.0       19.0       46.0
Dante             Inferno    11.6       12.6       24.2

我希望使用 meshgrid 之类的东西可以让我达到 75%。基本上,我想看看是否可以使用numpy(不是pandas)构建所有预计算值的结构来构建结构,以便我可以检索所有可能组合的上述结果。为了简单起见,我们只考虑SUM 作为唯一可能的计算。也许这是一种圆滑的询问方式,但是numpy 可以成为这样做的骨干,还是我需要使用其他东西?

最后,如果在numpy 中不可能,它如何存储在 MDA 中?

【问题讨论】:

  • 什么是“立方体模型”?您可以在此处链接一些参考资料吗?
  • @KarlKnechtel 很难用几句话来概括,但基本上它就像 excel 中的数据透视表,其中“维度”(例如作者、书籍和年份)用于分组数据,然后“度量”用于计算每个维度组合的某些内容。但简而言之,它是一个“数据透视表”。这是维基百科,它有一个相当不错的摘要:en.wikipedia.org/wiki/OLAP_cube
  • numpy 旨在处理非结构化数字类型。我原以为这会自动取消它的资格
  • 这看起来更像是 Pandas 的工作,它建立在 NumPy 之上。你是致力于 NumPy 还是可以选择?
  • @Timus 我敢打赌 pandas 可以很容易地做到这一点,但我希望看到一个比没有那么多抽象的级别更低的级别(pandas.pivot 是一个好的开始,我非常使用它一点)。

标签: python numpy multidimensional-array olap olap-cube


【解决方案1】:

我认为numpy记录数组可以用于这个任务,下面是我基于记录数组的解决方案。

class rec_array():
    
    def __init__(self,author=None,book=None,year=None,sales=None):
        self.dtype = [('author','<U20'), ('book','<U20'),('year','<U20'),('sales',float)]
        self.rec_array = np.rec.fromarrays((author,book,year,sales),dtype=self.dtype)
        
    def add_record(self,author,book,year,sales):
        new_rec = np.rec.fromarrays((author,book,year,sales),dtype=self.dtype)
        if not self.rec_array.shape == ():
            self.rec_array = np.hstack((self.rec_array,new_rec))
        else:
            self.rec_array = new_rec
    
    def get_view(self,conditions):
        """
        conditions: 
            A list of conditions, for example 
            [["author",<,"Shakespeare"],["year","<=","2000"]]
        """
        mask = np.ones(self.rec_array.shape[0]).astype(bool)
        for item in conditions:
            field,op,target = item
            field_op = "self.rec_array['%s'] %s '%s'" % (field,op,target)
            mask &= eval(field_op)
        
        selected_sales = self.rec_array['sales'][mask]
        
        return np.sum(selected_sales)

根据这个rec_array,给定数据

author = 4*["Shakespeare"]+ 2*["Dante"]
book = 2*["Hamlet"] + 2*["Romeo"] + 2*["Inferno"]
year = 3*["2000", "2001"]
sales = [104.2, 99.0, 27.0, 19.0, 11.6, 12.6]

我们创建一个实例

test = rec_array()
test.add_record(author,book,year,sales)

例如,如果您想要销售莎士比亚的罗密欧,您可以这样做

test.get_view([["author","==","Shakespeare"],["book","==","Romeo"]])

输出为 46.0

或者,你也可以这样做

test.get_view([["author","==","Shakespeare"],["year","<=","2000"]])

输出为 131.2

【讨论】:

    【解决方案2】:

    对于数据结构,您可以定义以下类:

    class Cube:
    
        def __init__(self, row_index, col_index, data):
            self.row_index = {r: i for i, r in enumerate(row_index)}
            self.col_index = {c: i for i, c in enumerate(col_index)}
            self.data = data
    
        def __getitem__(self, item):
            row, col = item
            return self.data[self.row_index[row] , self.col_index[col]]
    
        def __repr__(self):
            return repr(self.data)
    

    基本上是一个围绕二维 numpy 数组的轻量级包装器。为了计算交叉表,您可以执行以下操作:

    def _x_tab(rows, columns, values):
        """Function for computing the cross-tab of simple arrays"""
        unique_values_all_cols, idx = zip(*(np.unique(col, return_inverse=True) for col in [rows, columns]))
    
        shape_xt = [uniq_vals_col.size for uniq_vals_col in unique_values_all_cols]
    
        xt = np.zeros(shape_xt, dtype=np.float)
        np.add.at(xt, idx, values)
    
        return unique_values_all_cols, xt
    
    
    def make_index(a, r):
        """Make array of tuples"""
        l = [tuple(row) for row in a[:, r]]
        return make_object_array(l)
    
    
    def make_object_array(l):
        a = np.empty(len(l), dtype=object)
        a[:] = l
        return a
    
    
    def fill_label(ar, le):
        """Fill missing parts with ALL label"""
        missing = tuple(["ALL"] * le)
        return [(e + missing)[:le] for e in ar]
    
    def x_tab(rows, cols, values):
        """Main function for cross tabulation"""
        _, l_cols = rows.shape
    
        total_agg = []
        total_idx = []
        for i in range(l_cols + 1):
            (idx, _), agg = _x_tab(make_index(rows, list(range(i))), cols, values)
            total_idx.extend(fill_label(idx, l_cols))
            total_agg.append(agg)
    
        stacked_agg = np.vstack(total_agg)
        stacked_agg_total = stacked_agg.sum(axis=1).reshape(-1, 1)
    
        return Cube(total_idx, list(dict.fromkeys(cols)), np.concatenate((stacked_agg, stacked_agg_total), axis=1))
    

    假设输入一个arr 数组:

    [['Shakespeare' 'Hamlet' 2000 104.2]
     ['Shakespeare' 'Hamlet' 2001 99.0]
     ['Shakespeare' 'Romeo' 2000 27.0]
     ['Shakespeare' 'Romeo' 2001 19.0]
     ['Dante' 'Inferno' 2000 11.6]
     ['Dante' 'Inferno' 2001 12.6]]
    

    那么x_tab可以这样调用:

    result = x_tab(arr[:, [0, 1]], arr[:, 2], arr[:, 3])
    print(result)
    

    输出

    array([[142.8, 130.6, 273.4],
           [ 11.6,  12.6,  24.2],
           [131.2, 118. , 249.2],
           [ 11.6,  12.6,  24.2],
           [104.2,  99. , 203.2],
           [ 27. ,  19. ,  46. ]])
    

    请注意,此表示 (repr) 仅用于显示结果,您可以根据需要更改它。然后您可以按如下方式访问多维数据集的单元格:

    print(result[('Dante', 'ALL'), 2001])
    print(result[('Dante', 'Inferno'), 2001])
    print(result[('Shakespeare', 'Hamlet'), 2000])
    

    输出

    12.6
    12.6
    104.2
    

    请注意,大部分操作都在 _x_tab 函数中,该函数使用纯 numpy 函数。同时它为您选择的任何聚合函数提供了灵活的接口,只需在此行更改ufunc

    np.add.at(xt, idx, values)
    

    来自此list 的任何其他人。有关详细信息,请参阅at 运算符的文档。

    可以在here 找到代码的工作副本。以上就是基于这个gist

    注意 这假设您正在为索引传递多个列(rows 参数)。

    【讨论】:

      【解决方案3】:

      这里是一个解决方案的草图,显然你会包装在帮助函数和类中以提供一个简单的界面。这个想法是将每个唯一名称映射到一个索引(为简单起见,此处为顺序),然​​后将其用作索引以将值存储在数组中。这是次优的,因为您必须将数组填充到最大数量的不同项目的最大大小。否则数组为零,因此不要包含在总和中。如果您想避免添加零元素,可以考虑使用掩码数组和掩码总和。

      import numpy as np
      
      def get_dict(x):
          return {a:i for i, a in enumerate(set(x))}
      
      #Mapping name to unique contiguous numbers (obviously put in a fn or class)
      author = 4*["Shakespeare"]+ 2*["Dante"]
      book = 2*["Hamlet"] + 2*["Romeo"] + 2*["Inferno"]
      year = 3*["2000", "2001"]
      sales = [104.2, 99.0, 27.0, 19.0, 11.6, 12.6]
      
      #Define dictonary of indices
      d = get_dict(author)
      d.update(get_dict(book))
      d.update(get_dict(year)) 
      
      #Index values to put in multi-dimension array
      ai = [d[i] for i in author]
      bi = [d[i] for i in book]
      yi = [d[i] for i in year]
      
      #Pad array up to maximum size
      A = np.zeros([np.max(ai)+1, np.max(bi)+1, np.max(yi)+1])
      
      #Store elements with unique name as index in 3D datacube
      for n in range(len(sales)):
          i = ai[n]; j = bi[n]; k = yi[n]
          A[i,j,k] = sales[n]
      
      #Now we can get the various sums, for example all sales
      print("Total=", np.sum(A))
      
      #All shakespeare (0)
      print("All shakespeare=", np.sum(A[d["Shakespeare"],:,:]))
      
      #All year 2001
      print("All year 2001", np.sum(A[:,:,d["2001"]]))
      
      #All Shakespeare in 2000
      print("All Shakespeare in 2000", np.sum(A[d["Shakespeare"],:,d["2000"]]))
      

      【讨论】:

      • 有趣的方法,谢谢!能否请您简要解释一下aibiyi 变量是什么?
      • 抱歉,命名错误。只是对应于 unqiue 字符串的索引数组,即作者索引 (ai)、书籍索引 (bi) 和年份索引 (yi)。您将使用一个函数从字符串生成此散列,但这需要是连续的,否则您的数组中会出现间隙(并且 numpy 数组的优点是连续数据)
      【解决方案4】:

      只是类初始化:

      import numpy as np
      
      class Olap:
          def __init__(self, values, headers, *locators):
              self.labels = []
              self.indices = []
              self.headers = headers
              self.shape = (len(l) for l in locators)
              for loc in locators:
                  unique, ix = np.unique(loc, return_inverse = True)
                  self.labels.append(unique)
                  self.indices.append(ix)
              self.arr   = np.zeros(self.shape)
              self.count = np.zeros(self.shape, dtype = int)
              np.add.at(self.arr, tuple(self.indices), values)
              np.add.at(self.count, tuple(self.indices), np.ones(values.shape))
      
      author = 4*["Shakespeare"]+ 2*["Dante"]
      book = 2*["Hamlet"] + 2*["Romeo"] + 2*["Inferno"]
      year = 3*["2000", "2001"]
      sales = [104.2, 99.0, 27.0, 19.0, 11.6, 12.6]
      
      
      olap = Olap(sales, ["author", "book", "year"], author, book, year)
      

      从那里您可以使用self.arr.sum() 沿不同轴创建求和函数,甚至可以使用self.count.sum() 进行平均。您可能需要某种方式来添加更多数据(再次使用np.add.at 将它们放入arr) - 但您的数据结构现在是 Nd 而不是表格,这应该为高维数据带来相同的好处pivot 确实如此。

      不打算将所有这些都放入代码中(即使是 400 次代表),但一旦您制作了多维数据结构,它似乎并不会太复杂。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多