@@ -2324,6 +2324,163 @@ def replace(self, axes_to_replace=None, new_axis=None, inplace=False, **kwargs):
23242324 else :
23252325 return AxisCollection (axes )
23262326
2327+ def _guess_axis (self , axis_key ):
2328+ if isinstance (axis_key , Group ):
2329+ group_axis = axis_key .axis
2330+ if group_axis is not None :
2331+ # we have axis information but not necessarily an Axis object from self.axes
2332+ real_axis = self [group_axis ]
2333+ if group_axis is not real_axis :
2334+ axis_key = axis_key .with_axis (real_axis )
2335+ return axis_key
2336+
2337+ # TODO: instead of checking all axes, we should have a big mapping
2338+ # (in AxisCollection or LArray):
2339+ # label -> (axis, index)
2340+ # or possibly (for ambiguous labels)
2341+ # label -> {axis: index}
2342+ # but for Pandas, this wouldn't work, we'd need label -> axis
2343+ valid_axes = []
2344+ for axis in self :
2345+ try :
2346+ axis .index (axis_key )
2347+ valid_axes .append (axis )
2348+ except KeyError :
2349+ continue
2350+ if not valid_axes :
2351+ raise ValueError ("%s is not a valid label for any axis" % axis_key )
2352+ elif len (valid_axes ) > 1 :
2353+ valid_axes = ', ' .join (a .name if a .name is not None else '{{{}}}' .format (self .axes .index (a ))
2354+ for a in valid_axes )
2355+ raise ValueError ('%s is ambiguous (valid in %s)' % (axis_key , valid_axes ))
2356+ return valid_axes [0 ][axis_key ]
2357+
2358+ def set_labels (self , axis = None , labels = None , inplace = False , ** kwargs ):
2359+ r"""Replaces the labels of one or several axes.
2360+
2361+ Parameters
2362+ ----------
2363+ axis : string or Axis or dict
2364+ Axis for which we want to replace labels, or mapping {axis: changes} where changes can either be the
2365+ complete list of labels, a mapping {old_label: new_label} or a function to transform labels.
2366+ If there is no ambiguity (two or more axes have the same labels), `axis` can be a direct mapping
2367+ {old_label: new_label}.
2368+ labels : int, str, iterable or mapping or function, optional
2369+ Integer or list of values usable as the collection of labels for an Axis. If this is mapping, it must be
2370+ {old_label: new_label}. If it is a function, it must be a function accepting a single argument (a
2371+ label) and returning a single value. This argument must not be used if axis is a mapping.
2372+ inplace : bool, optional
2373+ Whether or not to modify the original object or return a new AxisCollection and leave the original intact.
2374+ Defaults to False.
2375+ **kwargs :
2376+ `axis`=`labels` for each axis you want to set labels.
2377+
2378+ Returns
2379+ -------
2380+ AxisCollection
2381+ AxisCollection with modified labels.
2382+
2383+ Examples
2384+ --------
2385+ >>> from larray import ndtest
2386+ >>> axes = AxisCollection('nat=BE,FO;sex=M,F')
2387+ >>> axes
2388+ AxisCollection([
2389+ Axis(['BE', 'FO'], 'nat'),
2390+ Axis(['M', 'F'], 'sex')
2391+ ])
2392+ >>> axes.set_labels('sex', ['Men', 'Women'])
2393+ AxisCollection([
2394+ Axis(['BE', 'FO'], 'nat'),
2395+ Axis(['Men', 'Women'], 'sex')
2396+ ])
2397+
2398+ when passing a single string as labels, it will be interpreted to create the list of labels, so that one can
2399+ use the same syntax than during axis creation.
2400+
2401+ >>> axes.set_labels('sex', 'Men,Women')
2402+ AxisCollection([
2403+ Axis(['BE', 'FO'], 'nat'),
2404+ Axis(['Men', 'Women'], 'sex')
2405+ ])
2406+
2407+ to replace only some labels, one must give a mapping giving the new label for each label to replace
2408+
2409+ >>> axes.set_labels('sex', {'M': 'Men'})
2410+ AxisCollection([
2411+ Axis(['BE', 'FO'], 'nat'),
2412+ Axis(['Men', 'F'], 'sex')
2413+ ])
2414+
2415+ to transform labels by a function, use any function accepting and returning a single argument:
2416+
2417+ >>> axes.set_labels('nat', str.lower)
2418+ AxisCollection([
2419+ Axis(['be', 'fo'], 'nat'),
2420+ Axis(['M', 'F'], 'sex')
2421+ ])
2422+
2423+ to replace labels for several axes at the same time, one should give a mapping giving the new labels for each
2424+ changed axis
2425+
2426+ >>> axes.set_labels({'sex': 'Men,Women', 'nat': 'Belgian,Foreigner'})
2427+ AxisCollection([
2428+ Axis(['Belgian', 'Foreigner'], 'nat'),
2429+ Axis(['Men', 'Women'], 'sex')
2430+ ])
2431+
2432+ or use keyword arguments
2433+
2434+ >>> axes.set_labels(sex='Men,Women', nat='Belgian,Foreigner')
2435+ AxisCollection([
2436+ Axis(['Belgian', 'Foreigner'], 'nat'),
2437+ Axis(['Men', 'Women'], 'sex')
2438+ ])
2439+
2440+ one can also replace some labels in several axes by giving a mapping of mappings
2441+
2442+ >>> axes.set_labels({'sex': {'M': 'Men'}, 'nat': {'BE': 'Belgian'}})
2443+ AxisCollection([
2444+ Axis(['Belgian', 'FO'], 'nat'),
2445+ Axis(['Men', 'F'], 'sex')
2446+ ])
2447+
2448+ when there is no ambiguity (two or more axes have the same labels), it is possible to give a mapping
2449+ between old and new labels
2450+
2451+ >>> axes.set_labels({'M': 'Men', 'BE': 'Belgian'})
2452+ AxisCollection([
2453+ Axis(['Belgian', 'FO'], 'nat'),
2454+ Axis(['Men', 'F'], 'sex')
2455+ ])
2456+ """
2457+ if axis is None :
2458+ changes = {}
2459+ elif isinstance (axis , dict ):
2460+ changes = axis
2461+ elif isinstance (axis , (basestring , Axis , int )):
2462+ changes = {axis : labels }
2463+ else :
2464+ raise ValueError ("Expected None or a string/int/Axis/dict instance for axis argument" )
2465+ changes .update (kwargs )
2466+ # TODO: we should implement the non-dict behavior in Axis.replace, so that we can simplify this code to:
2467+ # new_axes = [self[old_axis].replace(axis_changes) for old_axis, axis_changes in changes.items()]
2468+ new_axes = []
2469+ for old_axis , axis_changes in changes .items ():
2470+ try :
2471+ real_axis = self [old_axis ]
2472+ except KeyError :
2473+ axis_changes = {old_axis : axis_changes }
2474+ real_axis = self ._guess_axis (old_axis ).axis
2475+ if isinstance (axis_changes , dict ):
2476+ new_axis = real_axis .replace (axis_changes )
2477+ elif callable (axis_changes ):
2478+ new_axis = real_axis .apply (axis_changes )
2479+ else :
2480+ new_axis = Axis (axis_changes , real_axis .name )
2481+ new_axes .append ((real_axis , new_axis ))
2482+ return self .replace (new_axes , inplace = inplace )
2483+
23272484 # TODO: deprecate method (should use __sub__ instead)
23282485 def without (self , axes ):
23292486 """
0 commit comments