code: def apply(
          self,
          func,
          num_splits=None,
          other_axis_partition=None,
          maintain_partitioning=True,
          **kwargs
      ):
          
          if num_splits is None:
              num_splits = len(self.list_of_blocks)
  
          if other_axis_partition is not None:
              return self._wrap_partitions(
                  self.deploy_func_between_two_axis_partitions(
                      self.axis,
                      func,
                      num_splits,
                      len(self.list_of_blocks),
                      kwargs,
                      *tuple(self.list_of_blocks + other_axis_partition.list_of_blocks)
                  )
              )
          args = [self.axis, func, num_splits, kwargs, maintain_partitioning]
          args.extend(self.list_of_blocks)
          return self._wrap_partitions(self.deploy_axis_func(*args))
docs: Applies func to the object in the plasma store.
  
          See notes in Parent class about this method.
  
          Args:
              func: The function to apply.
              num_splits: The number of times to split the result object.
              other_axis_partition: Another `PandasOnRayFrameAxisPartition` object to apply to
                  func with this one.
              maintain_partitioning: Whether or not to keep the partitioning in the same
                  orientation as it was previously. This is important because we may be
                  operating on an individual AxisPartition and not touching the rest.
                  In this case, we have to return the partitioning to its previous
                  orientation (the lengths will remain the same). This is ignored between
                  two axis partitions.
  
          Returns:
              A list of `RayRemotePartition` objects.