Repository navigation
Redesign DataFrame structure #817
Changes from 1 commit
83a4630
efa54fc
677a673
2712d76
6468e82
6b86a95
0048c7c
6a42d51
926c123
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |
| import operator | ||
|
|
||
| import numba | ||
| from collections import namedtuple | ||
| from numba import types, cgutils | ||
| from numba.extending import (models, register_model, lower_cast, infer_getattr, | ||
| type_callable, infer, overload, intrinsic, | ||
|
|
@@ -52,6 +53,9 @@ def generic_resolve(self, df, attr): | |
| return SeriesType(arr_typ.dtype, arr_typ, df.index, True) | ||
|
|
||
|
|
||
| Column_id = namedtuple('Column_id', ('type_id', 'col_type_id')) | ||
|
|
||
|
|
||
| @intrinsic | ||
| def init_dataframe(typingctx, *args): | ||
| """Create a DataFrame with provided data, index and columns values. | ||
|
|
@@ -66,6 +70,28 @@ def init_dataframe(typingctx, *args): | |
| index_typ = args[n_cols] | ||
| column_names = tuple(a.literal_value for a in args[n_cols + 1:]) | ||
|
|
||
| # Define df structure, map column name to column position ex. {'A': (0,0), 'B': (1,0), 'C': (0,1)} | ||
| df_structure = {} | ||
| # Store unique types of columns ex. {'int64': (0, [0, 2]), 'float64': (1, [1])} | ||
| data_typs_map = {} | ||
| type_id = 0 | ||
| for col_id, col_typ in enumerate(data_typs): | ||
| col_name = column_names[col_id] | ||
|
|
||
| if col_typ not in data_typs_map: | ||
| data_typs_map[col_typ] = (type_id, [col_id]) | ||
| # The first column in each type always has 0 index | ||
| df_structure[col_name] = Column_id(type_id, 0) | ||
| else: | ||
| # Get index of column in list of types | ||
| col_idx_list = len(data_typs_map[col_typ][1]) | ||
| type_id = data_typs_map[col_typ][0] | ||
| df_structure[col_name] = Column_id(type_id, col_idx_list) | ||
|
|
||
| data_typs_map[col_typ][1].append(col_id) | ||
|
|
||
| type_id += 1 | ||
|
|
||
| def codegen(context, builder, signature, args): | ||
| in_tup = args[0] | ||
| data_arrs = [builder.extract_value(in_tup, i) for i in range(n_cols)] | ||
|
|
@@ -76,15 +102,23 @@ def codegen(context, builder, signature, args): | |
| dataframe = cgutils.create_struct_proxy( | ||
| signature.return_type)(context, builder) | ||
|
|
||
| data_list_type = [types.List(typ) for typ in data_typs_map.keys()] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure that the order of types in the dict will always match the order of columns in DF?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe more reliable to create extra list of types as it was done in the model. Then use the list for ordered iterating. |
||
|
|
||
| data_lists = [] | ||
| for typ_id, typ in enumerate(data_typs_map.keys()): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there no problem with order of lists in the tuple? |
||
| data_list_typ = context.build_list(builder, data_list_type[typ_id], | ||
| [data_arrs[data_id] for data_id in data_typs_map[typ][1]]) | ||
| data_lists.append(data_list_typ) | ||
|
|
||
| data_tup = context.make_tuple( | ||
| builder, types.Tuple(data_typs), data_arrs) | ||
| column_tup = context.make_tuple( | ||
| builder, types.UniTuple(string_type, n_cols), column_strs) | ||
| zero = context.get_constant(types.int8, 0) | ||
| builder, types.Tuple(data_list_type), data_lists) | ||
|
|
||
| col_list_type = types.List(string_type) | ||
| column_list = context.build_list(builder, col_list_type, column_strs) | ||
|
|
||
| dataframe.data = data_tup | ||
| dataframe.index = index | ||
| dataframe.columns = column_tup | ||
| dataframe.columns = column_list | ||
| dataframe.parent = context.get_constant_null(types.pyobject) | ||
|
|
||
| # increase refcount of stored values | ||
|
|
@@ -97,7 +131,7 @@ def codegen(context, builder, signature, args): | |
|
|
||
| return dataframe._getvalue() | ||
|
|
||
| ret_typ = DataFrameType(data_typs, index_typ, column_names) | ||
| ret_typ = DataFrameType(data_typs, index_typ, column_names, df_structure=df_structure) | ||
| sig = signature(ret_typ, types.Tuple(args)) | ||
| return sig, codegen | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -36,7 +36,7 @@ class DataFrameType(types.Type): # TODO: IterableType over column names | |||||
| """Temporary type class for DataFrame objects. | ||||||
| """ | ||||||
|
|
||||||
| def __init__(self, data=None, index=None, columns=None, has_parent=False): | ||||||
| def __init__(self, data=None, index=None, columns=None, has_parent=False, df_structure=None): | ||||||
| self.data = data | ||||||
| if index is None: | ||||||
| index = types.none | ||||||
|
|
@@ -45,6 +45,7 @@ def __init__(self, data=None, index=None, columns=None, has_parent=False): | |||||
| # keeping whether it is unboxed from Python to enable reflection of new | ||||||
| # columns | ||||||
| self.has_parent = has_parent | ||||||
| self.df_structure = df_structure | ||||||
| super(DataFrameType, self).__init__( | ||||||
| name="dataframe({}, {}, {}, {})".format(data, index, columns, has_parent)) | ||||||
|
|
||||||
|
|
@@ -85,11 +86,18 @@ def is_precise(self): | |||||
| @register_model(DataFrameType) | ||||||
| class DataFrameModel(models.StructModel): | ||||||
| def __init__(self, dmm, fe_type): | ||||||
| n_cols = len(fe_type.columns) | ||||||
| types_unique = set() | ||||||
| df_types = [] | ||||||
| for col_id, col_type in enumerate(fe_type.data): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| if col_type in types_unique: | ||||||
| continue | ||||||
| types_unique.add(col_type) | ||||||
| df_types.append(col_type) | ||||||
|
|
||||||
| members = [ | ||||||
| ('data', types.Tuple(fe_type.data)), | ||||||
| ('data', types.Tuple([types.List(typ) for typ in df_types])), | ||||||
| ('index', fe_type.index), | ||||||
| ('columns', types.UniTuple(string_type, n_cols)), | ||||||
| ('columns', types.List(string_type)), | ||||||
| ('parent', types.pyobject), | ||||||
| ] | ||||||
| super(DataFrameModel, self).__init__(dmm, fe_type, members) | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mixed CamelCase and snake_case in one palce. I would propose to use CamelCase only for named tuples.
Also the definition of named tuple for me looks better in the following representation:
But it's up to you!