17
17
from pandas .core .internals import (BlockManager ,
18
18
create_block_manager_from_arrays ,
19
19
create_block_manager_from_blocks )
20
+ from pandas .core .series import Series
20
21
from pandas .core .frame import DataFrame
21
22
from pandas .core .generic import NDFrame , _shared_docs
23
+ from pandas .tools .util import cartesian_product
22
24
from pandas import compat
23
25
from pandas .util .decorators import deprecate , Appender , Substitution
24
26
import pandas .core .common as com
@@ -333,26 +335,34 @@ def axis_pretty(a):
333
335
[class_name , dims ] + [axis_pretty (a ) for a in self ._AXIS_ORDERS ])
334
336
return output
335
337
336
- def _get_plane_axes (self , axis ):
338
+ def _get_plane_axes_index (self , axis ):
337
339
"""
338
- Get my plane axes: these are already
340
+ Get my plane axes indexes : these are already
339
341
(as compared with higher level planes),
340
- as we are returning a DataFrame axes
342
+ as we are returning a DataFrame axes indexes
341
343
"""
342
- axis = self ._get_axis_name (axis )
344
+ axis_name = self ._get_axis_name (axis )
343
345
344
- if axis == 'major_axis' :
345
- index = self . minor_axis
346
- columns = self . items
347
- if axis == 'minor_axis' :
348
- index = self . major_axis
349
- columns = self . items
350
- elif axis == 'items' :
351
- index = self . major_axis
352
- columns = self . minor_axis
346
+ if axis_name == 'major_axis' :
347
+ index = ' minor_axis'
348
+ columns = ' items'
349
+ if axis_name == 'minor_axis' :
350
+ index = ' major_axis'
351
+ columns = ' items'
352
+ elif axis_name == 'items' :
353
+ index = ' major_axis'
354
+ columns = ' minor_axis'
353
355
354
356
return index , columns
355
357
358
+ def _get_plane_axes (self , axis ):
359
+ """
360
+ Get my plane axes indexes: these are already
361
+ (as compared with higher level planes),
362
+ as we are returning a DataFrame axes
363
+ """
364
+ return [ self ._get_axis (axi ) for axi in self ._get_plane_axes_index (axis ) ]
365
+
356
366
fromDict = from_dict
357
367
358
368
def to_sparse (self , fill_value = None , kind = 'block' ):
@@ -431,6 +441,10 @@ def as_matrix(self):
431
441
self ._consolidate_inplace ()
432
442
return self ._data .as_matrix ()
433
443
444
+ @property
445
+ def dtypes (self ):
446
+ return self .apply (lambda x : x .dtype , axis = 'items' )
447
+
434
448
#----------------------------------------------------------------------
435
449
# Getting and setting elements
436
450
@@ -827,25 +841,104 @@ def to_frame(self, filter_observations=True):
827
841
to_long = deprecate ('to_long' , to_frame )
828
842
toLong = deprecate ('toLong' , to_frame )
829
843
830
- def apply (self , func , axis = 'major' ):
844
+ def apply (self , func , axis = 'major' , args = (), ** kwargs ):
831
845
"""
832
- Apply
846
+ Applies function along input axis of the Panel
833
847
834
848
Parameters
835
849
----------
836
- func : numpy function
837
- Signature should match numpy.{sum, mean, var, std} etc.
850
+ func : function
851
+ Function to apply to each combination of 'other' axes
852
+ e.g. if axis = 'items', then the combination of major_axis/minor_axis
853
+ will be passed a Series
838
854
axis : {'major', 'minor', 'items'}
839
- fill_value : boolean, default True
840
- Replace NaN values with specified first
855
+ args : tuple
856
+ Positional arguments to pass to function in addition to the
857
+ array/series
858
+ Additional keyword arguments will be passed as keywords to the function
859
+
860
+ Examples
861
+ --------
862
+ >>> p.apply(numpy.sqrt) # returns a Panel
863
+ >>> p.apply(lambda x: x.sum(), axis=0) # equiv to p.sum(0)
864
+ >>> p.apply(lambda x: x.sum(), axis=1) # equiv to p.sum(1)
865
+ >>> p.apply(lambda x: x.sum(), axis=2) # equiv to p.sum(2)
841
866
842
867
Returns
843
868
-------
844
- result : DataFrame or Panel
869
+ result : Pandas Object
845
870
"""
846
- i = self ._get_axis_number (axis )
847
- result = np .apply_along_axis (func , i , self .values )
848
- return self ._wrap_result (result , axis = axis )
871
+ axis = self ._get_axis_number (axis )
872
+ axis_name = self ._get_axis_name (axis )
873
+ ax = self ._get_axis (axis )
874
+ values = self .values
875
+ ndim = self .ndim
876
+
877
+ if args or kwargs and not isinstance (func , np .ufunc ):
878
+ f = lambda x : func (x , * args , ** kwargs )
879
+ else :
880
+ f = func
881
+
882
+ # try ufunc like
883
+ if isinstance (f , np .ufunc ):
884
+ try :
885
+ result = np .apply_along_axis (func , axis , values )
886
+ return self ._wrap_result (result , axis = axis )
887
+ except (AttributeError ):
888
+ pass
889
+
890
+ # iter thru the axes
891
+ slice_axis = self ._get_axis (axis )
892
+ slice_indexer = [0 ]* (ndim - 1 )
893
+ indexer = np .zeros (ndim , 'O' )
894
+ indlist = list (range (ndim ))
895
+ indlist .remove (axis )
896
+ indexer [axis ] = slice (None , None )
897
+ indexer .put (indlist , slice_indexer )
898
+ planes = [ self ._get_axis (axi ) for axi in indlist ]
899
+ shape = np .array (self .shape ).take (indlist )
900
+
901
+ # all the iteration points
902
+ points = cartesian_product (planes )
903
+
904
+ results = []
905
+ for i in xrange (np .prod (shape )):
906
+
907
+ # construct the object
908
+ pts = tuple ([ p [i ] for p in points ])
909
+ indexer .put (indlist , slice_indexer )
910
+
911
+ obj = Series (values [tuple (indexer )],index = slice_axis ,name = pts )
912
+ result = func (obj , * args , ** kwargs )
913
+
914
+ results .append (result )
915
+
916
+ # increment the indexer
917
+ slice_indexer [- 1 ] += 1
918
+ n = - 1
919
+ while (slice_indexer [n ] >= shape [n ]) and (n > (1 - ndim )):
920
+ slice_indexer [n - 1 ] += 1
921
+ slice_indexer [n ] = 0
922
+ n -= 1
923
+
924
+ # empty object
925
+ if not len (results ):
926
+ return self ._constructor (** self ._construct_axes_dict ())
927
+
928
+ # same ndim as current
929
+ if isinstance (results [0 ],Series ):
930
+ arr = np .vstack ([ r .values for r in results ])
931
+ arr = arr .T .reshape (tuple ([len (slice_axis )] + list (shape )))
932
+ tranp = np .array ([axis ]+ indlist ).argsort ()
933
+ arr = arr .transpose (tuple (list (tranp )))
934
+ return self ._constructor (arr ,** self ._construct_axes_dict ())
935
+
936
+ # ndim-1 shape
937
+ results = np .array (results ).reshape (shape )
938
+ if results .ndim == 2 and axis_name != self ._info_axis_name :
939
+ results = results .T
940
+ planes = planes [::- 1 ]
941
+ return self ._construct_return_type (results ,planes )
849
942
850
943
def _reduce (self , op , axis = 0 , skipna = True , numeric_only = None ,
851
944
filter_type = None , ** kwds ):
@@ -863,21 +956,41 @@ def _reduce(self, op, axis=0, skipna=True, numeric_only=None,
863
956
864
957
def _construct_return_type (self , result , axes = None , ** kwargs ):
865
958
""" return the type for the ndim of the result """
866
- ndim = result .ndim
867
- if self .ndim == ndim :
959
+ ndim = getattr (result ,'ndim' ,None )
960
+
961
+ # need to assume they are the same
962
+ if ndim is None :
963
+ if isinstance (result ,dict ):
964
+ ndim = getattr (result .values ()[0 ],'ndim' ,None )
965
+
966
+ # a saclar result
967
+ if ndim is None :
968
+ ndim = 0
969
+
970
+ # have a dict, so top-level is +1 dim
971
+ else :
972
+ ndim += 1
973
+
974
+ # scalar
975
+ if ndim == 0 :
976
+ return Series (result )
977
+
978
+ # same as self
979
+ elif self .ndim == ndim :
868
980
""" return the construction dictionary for these axes """
869
981
if axes is None :
870
982
return self ._constructor (result )
871
983
return self ._constructor (result , ** self ._construct_axes_dict ())
872
984
985
+ # sliced
873
986
elif self .ndim == ndim + 1 :
874
987
if axes is None :
875
988
return self ._constructor_sliced (result )
876
989
return self ._constructor_sliced (
877
990
result , ** self ._extract_axes_for_slice (self , axes ))
878
991
879
992
raise PandasError ('invalid _construct_return_type [self->%s] '
880
- '[result->%s]' % (self . ndim , result . ndim ))
993
+ '[result->%s]' % (self , result ))
881
994
882
995
def _wrap_result (self , result , axis ):
883
996
axis = self ._get_axis_name (axis )
0 commit comments