docs: def aggregate(self, region_agg=None, sector_agg=None,
                    region_names=None, sector_names=None,
                    inplace=True, pre_aggregation=False):
          """ Aggregates the IO system.
  
              Aggregation can be given as vector (use pymrio.build_agg_vec) or
              aggregation matrix. In the case of a vector this must be of length
              self.get_regions() / self.get_sectors() respectively with the new
              position as integer or a string of the new name. In the case of
              strings the final output order can be specified in region_dict and
              sector_dict in the format {str1 = int_pos, str2 = int_pos, ...}.
  
              If the sector / region concordance is given as matrix or numerical
              vector, generic names will be used for the new sectors/regions. One
              can define specific names by defining the aggregation as string
              vector
  
              Parameters
              ----------
              region_agg : list, array or string, optional
                  The aggregation vector or matrix for the regions (np.ndarray or
                  list). If string: aggregates to one total region and names is
                  to the given string.
                  Pandas Dataframe with columns 'orignal' and 'aggregated'.
                  This is the output from the country_converter.agg_conc
              sector_agg : list, arrays or string, optional
                  The aggregation vector or matrix for the sectors (np.ndarray or
                  list).If string: aggregates to one total region and names is
                  to the given string.
              region_names : list, optional
                  Names for the aggregated regions.
                  If concordance matrix - in order of rows in this matrix
                  If concordance vector - in order or num. values in this vector
                  If string based - same order as the passed string
                  Not considered if passing a DataFrame - in this case give the
                  names in the column 'aggregated'
  
              sector_names : list, optional
                  Names for the aggregated sectors. Same behaviour as
                  'region_names'
  
              inplace : boolean, optional
                  If True, aggregates the IOSystem in place (default),
                  otherwise aggregation happens on a copy of the IOSystem.
                  Regardless of the setting, the IOSystem is returned to
                  allow for chained operations.
  
              Returns
              -------
              IOSystem
                  Aggregated IOSystem (if inplace is False)
  
          """
          # Development note: This can not be put in the CoreSystem b/c
          # than the recalculation of the extension coefficients would not
          # work.
  
          if not inplace:
              self = self.copy()
  
          try:
              self.reset_to_flows()
          except ResetError:
              raise AggregationError("System under-defined for aggregation - "
                                     "do a 'calc_all' before aggregation")
  
          if type(region_names) is str:
              region_names = [region_names]
          if type(sector_names) is str:
              sector_names = [sector_names]
  
          if type(region_agg) is pd.DataFrame:
              if (('original' not in region_agg.columns) or
                      ('aggregated' not in region_agg.columns)):
                  raise ValueError('Passed DataFrame must include the columns '
                                   '"original" and "aggregated"')
              region_agg = (region_agg
                            .set_index('original')
                            .reindex(self.get_regions(),
                                     fill_value=MISSING_AGG_ENTRY['region'])
                            .loc[:, 'aggregated'])
  
          if type(sector_agg) is pd.DataFrame:
              if (('original' not in sector_agg.columns) or
                      ('aggregated' not in sector_agg.columns)):
                  raise ValueError('Passed DataFrame must include the columns '
                                   '"original" and "aggregated"')
              sector_agg = (sector_agg
                            .set_index('original')
                            .reindex(self.get_sectors(),
                                     fill_value=MISSING_AGG_ENTRY['sector'])
                            .loc[:, 'aggregated'])
  
          # fill the aggregation matrix with 1:1 mapping
          # if input not given and get names if not given
          _same_regions = False
          _same_sectors = False
          if region_agg is None:
              region_agg = self.get_regions()
              region_names = region_names or self.get_regions()
              _same_regions = True
          if sector_agg is None:
              sector_agg = self.get_sectors()
              sector_names = sector_names or self.get_sectors()
              _same_sectors = True
  
          # capture total aggregation case
          if type(region_agg) is str:
              region_agg = [region_agg] * len(self.get_regions())
          if type(sector_agg) is str:
              sector_agg = [sector_agg] * len(self.get_sectors())
  
          if ioutil.is_vector(region_agg):
              region_conc = ioutil.build_agg_matrix(region_agg)
          else:
              region_conc = region_agg
          if ioutil.is_vector(sector_agg):
              sector_conc = ioutil.build_agg_matrix(sector_agg)
          else:
              sector_conc = sector_agg
  
          # build the new names
          if (not _same_regions) and (not region_names):
                  if isinstance(region_agg, np.ndarray):
                      region_agg = region_agg.flatten().tolist()
                  if type(region_agg[0]) is str:
                      region_names = ioutil.unique_element(region_agg)
                  else:
                      # rows in the concordance matrix give the new number of
                      # regions
                      region_names = [GENERIC_NAMES['region'] +
                                      str(nr) for nr in
                                      range(region_conc.shape[0])]
  
          if (not _same_sectors) and (not sector_names):
                  if isinstance(sector_agg, np.ndarray):
                      sector_agg = (sector_agg.flatten().tolist())
                  if type(sector_agg[0]) is str:
                      sector_names = ioutil.unique_element(sector_agg)
                  else:
                      sector_names = [GENERIC_NAMES['sector'] +
                                      str(nr) for nr in
                                      range(sector_conc.shape[0])]
  
          # Assert right shapes
          if not sector_conc.shape[1] == len(self.get_sectors()):
              raise ValueError('Sector aggregation does not '
                               'correspond to the number of sectors.')
          if not region_conc.shape[1] == len(self.get_regions()):
              raise ValueError('Region aggregation does not '
                               'correspond to the number of regions.')
          if not len(sector_names) == sector_conc.shape[0]:
              raise ValueError('New sector names do not '
                               'match sector aggregation.')
          if not len(region_names) == region_conc.shape[0]:
              raise ValueError('New region names do not '
                               'match region aggregation.')
  
          # build pandas.MultiIndex for the aggregated system
          _reg_list_for_sec = [[r] * sector_conc.shape[0] for r in region_names]
          _reg_list_for_sec = [entry for entrylist in
                               _reg_list_for_sec for entry in entrylist]
  
          _reg_list_for_Ycat = [[r] * len(self.get_Y_categories()) for r in
                                region_names]
          _reg_list_for_Ycat = [entry for entrylist in
                                _reg_list_for_Ycat for entry in entrylist]
  
          _sec_list = list(sector_names) * region_conc.shape[0]
          _Ycat_list = list(self.get_Y_categories()) * region_conc.shape[0]
  
          mi_reg_sec = pd.MultiIndex.from_arrays(
                  [_reg_list_for_sec, _sec_list],
                  names=['region', 'sector'])
          mi_reg_Ycat = pd.MultiIndex.from_arrays(
                  [_reg_list_for_Ycat, _Ycat_list],
                  names=['region', 'category'])
  
          # arrange the whole concordance matrix
          conc = np.kron(region_conc, sector_conc)
          conc_y = np.kron(region_conc, np.eye(len(self.get_Y_categories())))
  
          # Aggregate
          self.meta._add_modify('Aggregate final demand y')
          self.Y = pd.DataFrame(
                      data=conc.dot(self.Y).dot(conc_y.T),
                      index=mi_reg_sec,
                      columns=mi_reg_Ycat,
                      )
  
          self.meta._add_modify('Aggregate transaction matrix Z')
          self.Z = pd.DataFrame(
                      data=conc.dot(self.Z).dot(conc.T),
                      index=mi_reg_sec,
                      columns=mi_reg_sec,
                      )
  
          if self.x is not None:
              # x could also be obtained from the
              # aggregated Z, but aggregate if available
              self.x = pd.DataFrame(
                          data=conc.dot(self.x),
                          index=mi_reg_sec,
                          columns=self.x.columns,
                          )
              self.meta._add_modify('Aggregate industry output x')
          else:
              self.x = calc_x(self.Z, self.Y)
  
          if self.population is not None:
              self.meta._add_modify('Aggregate population vector')
              self.population = pd.DataFrame(
                  data=region_conc.dot(self.population.T).T,
                  columns=region_names,
                  index=self.population.index,
                  )
  
          for extension in self.get_extensions(data=True):
              self.meta._add_modify('Aggregate extensions...')
              extension.reset_to_flows()
              st_redo_unit = False
              for ik_name, ik_df in zip(
                      extension.get_DataFrame(data=False, with_unit=False),
                      extension.get_DataFrame(data=True, with_unit=False)):
  
                  # Without unit - this is reset aftwards if necessary
                  if ik_df.index.names == ['region',
                                           'sector'] == ik_df.columns.names:
                      # Full disaggregated extensions - aggregate both axis
                      # (this is the case if the extions shows the flows from
                      # pda to cba)
                      extension.__dict__[ik_name] = pd.DataFrame(
                          data=conc.dot(ik_df).dot(conc.T))
  
                      # next step must be done afterwards due to unknown reasons
                      extension.__dict__[ik_name].columns = mi_reg_sec
                      extension.__dict__[ik_name].index = mi_reg_sec
                      st_redo_unit = True
                  elif (ik_df.index.names == ['region', 'sector'] and
                        ik_df.columns.names == ['region', 'category']):
  
                      # Full disaggregated finald demand satellite account.
                      # Thats not implemented yet - but aggregation is in place
                      extension.__dict__[ik_name] = pd.DataFrame(
                          data=conc.dot(ik_df).dot(conc_y.T))
                      # next step must be done afterwards due to unknown reasons
                      extension.__dict__[ik_name].columns = mi_reg_Ycat
                      extension.__dict__[ik_name].index = mi_reg_sec
  
                  elif ik_df.columns.names == ['region', 'category']:
                      # Satellite account connected to final demand (e.g. FY)
                      extension.__dict__[ik_name] = pd.DataFrame(
                          data=ik_df.dot(conc_y.T))
                      # next step must be done afterwards due to unknown reasons
                      extension.__dict__[ik_name].columns = mi_reg_Ycat
                      extension.__dict__[ik_name].index = ik_df.index
  
                  else:
                      # Standard case - aggregated columns, keep stressor rows
                      extension.__dict__[ik_name] = pd.DataFrame(
                          data=ik_df.dot(conc.T))
                      # next step must be done afterwards due to unknown reasons
                      extension.__dict__[ik_name].columns = mi_reg_sec
                      extension.__dict__[ik_name].index = ik_df.index
  
                  if st_redo_unit:
                      try:
                          _value = extension.unit.iloc[0].tolist()[0]
                          extension.unit = pd.DataFrame(
                                  index=mi_reg_sec,
                                  columns=extension.unit.columns,
                                  data=_value)
                      except AttributeError:
                          # could fail if no unit available
                          extension.unit = None
          self.calc_extensions()
          return self