Repository: astropy/astropy

Issue:
Clearer naming of velocities in coordinates?
Inspired by #6425: would it make sense to have a default name for velocities in coordinates that is clearer than, e.g., `d_phi`? This comes directly from the `Differential`, where it is logical, since the differential need not be a first-order time derivative. But for coordinates, the velocities are known to be first-order time derivatives, and perhaps a better name is useful. Right now, we translate names for common frames, but not for less common ones (such as cylindrical in #6425). One could imaging having a standardized "default" translation, such as, `d_X_dt` suggested by @jobovy in #6425. Or be a bit fancier, and translate everything that has units of length/time as `v_X` and everything that has angle/time as `pm_X`.

@adrn, @eteq?

EDIT: independently, we should what is done is documented more clearly.

Discussion:
astropy-bot[bot]: Hi there @adrn :wave: - thanks for the pull request! I'm just a friendly :robot: that checks for issues related to the changelog and making sure that this pull request is milestoned and labeled correctly. This is mainly intended for the maintainers, so if you are not a maintainer you can ignore this, and a maintainer will let you know if any action is required on your part :smiley:.

Everything looks good from my point of view! :+1:

*If there are any issues with this message, please report them [here](https://github.com/astropy/astropy-bot/issues).*
adrn: I cleaned up the code based on your comments - I guess I wrote it too hastily 😉. Anyways, I tried to simplify the logic a bit; see what you think now. 

adrn: Argh, in debugging the failures, I realized there is a fundamental flaw with what I have here. We support string names OR classes when defining the `frame_specific_representation_info` dict on Frame subclasses. So we don't know a priori which the user has specified, but we kind of want to be agnostic and let the user define and then get either `repr_info['spherical`]` or `repr_info[SphericalRepresentation]` no matter what they have defined. 

One option is to make `frame_specific_representation_info` a dictionary subclass that allows keying on a string or a class but only stores one internally. Another option is to add a lot of if statements to the frame meta class. Thoughts @mhvk?
mhvk: Wouldn't the lazy solution be to just store the same information keyed by both class and by string?
adrn: > Wouldn't the lazy solution be to just store the same information keyed by both class and by string?

That was my first approach, but somewhere an obscure error was cropping up when there were duplicates. i.e. I tried having `"spherical"` and `SphericalRepresentation` keys, but this causes something to fail. Will look in to fixing that instead!
adrn: Another note: when this is added, we can clean up the Sagittarius example (remove some boilerplate code in the class definition) http://docs.astropy.org/en/latest/generated/examples/coordinates/plot_sgr-coordinate-frame.html
eteq: @adrn - I thought  we *had* to have it be the classes because that's the only way to distinguish e.g. `CartesianRepresentation` and `CartesianDifferential`?  So that the string short-hands were only for the repesentations?
adrn: We do, going forward. There is code (e.g., in Sunpy @Cadair) that still uses the string keys for representations, so I'm more thinking about backwards-compatibility.
Cadair: Just to clarify here, you are saying that we should change e.g. this code: https://github.com/sunpy/sunpy/blob/master/sunpy/coordinates/frames.py#L85 to be class objects not strings?
adrn: Travis failure unrelated: Sesame is down so `from_name()` is failing in the tests.
adrn: I think this is ready for review.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index d7b0f3fea7..eba28c998b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -32,6 +32,11 @@ astropy.coordinates
 - SkyCoord objects now support storing and tranforming differentials - i.e.,
   both radial velocities and proper motions. [#6944]
 
+- All frame classes now automatically get sensible representation mappings for
+  velocity components. For example, ``d_x``, ``d_y``, ``d_z`` are all
+  automatically mapped to frame component namse ``v_x``, ``v_y``, ``v_z``.
+  [#6856]
+
 astropy.cosmology
 ^^^^^^^^^^^^^^^^^
 
diff --git a/astropy/coordinates/baseframe.py b/astropy/coordinates/baseframe.py
index 96f3a3ee8c..19fdf27af1 100644
--- a/astropy/coordinates/baseframe.py
+++ b/astropy/coordinates/baseframe.py
@@ -164,7 +164,8 @@ class FrameMeta(OrderedDescriptorContainer, abc.ABCMeta):
 
             if (not found_repr_info and
                     '_frame_specific_representation_info' in m):
-                repr_info = m['_frame_specific_representation_info']
+                # create a copy of the dict so we don't mess with the contents
+                repr_info = m['_frame_specific_representation_info'].copy()
                 found_repr_info = True
 
             if found_default_repr and found_default_diff and found_repr_info:
@@ -174,6 +175,75 @@ class FrameMeta(OrderedDescriptorContainer, abc.ABCMeta):
                 'Could not find all expected BaseCoordinateFrame class '
                 'attributes.  Are you mis-using FrameMeta?')
 
+        # Unless overridden via `frame_specific_representation_info`, velocity
+        # name defaults are (see also docstring for BaseCoordinateFrame):
+        #   * ``pm_{lon}_cos{lat}``, ``pm_{lat}`` for
+        #     `SphericalCosLatDifferential` proper motion components
+        #   * ``pm_{lon}``, ``pm_{lat}`` for `SphericalDifferential` proper
+        #     motion components
+        #   * ``radial_velocity`` for any `d_distance` component
+        #   * ``v_{x,y,z}`` for `CartesianDifferential` velocity components
+        # where `{lon}` and `{lat}` are the frame names of the angular
+        # components.
+        if repr_info is None:
+            repr_info = {}
+
+        for cls_or_name in repr_info.keys():
+            if isinstance(cls_or_name, str):
+                # TODO: this provides a layer of backwards compatibility in
+                # case the key is a string, but now we want explicit classes.
+                cls = _get_repr_cls(cls_or_name)
+                repr_info[cls] = repr_info.pop(cls_or_name)
+
+        # The default spherical names are 'lon' and 'lat'
+        repr_info.setdefault(r.SphericalRepresentation,
+                             [RepresentationMapping('lon', 'lon'),
+                              RepresentationMapping('lat', 'lat')])
+
+        sph_component_map = {m.reprname: m.framename
+                             for m in repr_info[r.SphericalRepresentation]}
+
+        repr_info.setdefault(r.SphericalCosLatDifferential, [
+            RepresentationMapping(
+                'd_lon_coslat',
+                'pm_{lon}_cos{lat}'.format(**sph_component_map),
+                u.mas/u.yr),
+            RepresentationMapping('d_lat',
+                                  'pm_{lat}'.format(**sph_component_map),
+                                  u.mas/u.yr),
+            RepresentationMapping('d_distance', 'radial_velocity',
+                                  u.km/u.s)
+        ])
+
+        repr_info.setdefault(r.SphericalDifferential, [
+            RepresentationMapping('d_lon',
+                                  'pm_{lon}'.format(**sph_component_map),
+                                  u.mas/u.yr),
+            RepresentationMapping('d_lat',
+                                  'pm_{lat}'.format(**sph_component_map),
+                                  u.mas/u.yr),
+            RepresentationMapping('d_distance', 'radial_velocity',
+                                  u.km/u.s)
+        ])
+
+        repr_info.setdefault(r.CartesianDifferential, [
+            RepresentationMapping('d_x', 'v_x', u.km/u.s),
+            RepresentationMapping('d_y', 'v_y', u.km/u.s),
+            RepresentationMapping('d_z', 'v_z', u.km/u.s)])
+
+        # Unit* classes should follow the same naming conventions
+        # TODO: this adds some unnecessary mappings for the Unit classes, so
+        # this could be cleaned up, but in practice doesn't seem to have any
+        # negative side effects
+        repr_info.setdefault(r.UnitSphericalRepresentation,
+                             repr_info[r.SphericalRepresentation])
+
+        repr_info.setdefault(r.UnitSphericalCosLatDifferential,
+                             repr_info[r.SphericalCosLatDifferential])
+
+        repr_info.setdefault(r.UnitSphericalDifferential,
+                             repr_info[r.SphericalDifferential])
+
         # Make read-only properties for the frame class attributes that should
         # be read-only to make them immutable after creation.
         # We copy attributes instead of linking to make sure there's no
@@ -252,6 +322,18 @@ class BaseCoordinateFrame(ShapedLikeNDArray, metaclass=FrameMeta):
         names and default units should be used on this frame for the components
         of that representation.
 
+    Unless overridden via `frame_specific_representation_info`, velocity name
+    defaults are:
+
+      * ``pm_{lon}_cos{lat}``, ``pm_{lat}`` for `SphericalCosLatDifferential`
+        proper motion components
+      * ``pm_{lon}``, ``pm_{lat}`` for `SphericalDifferential` proper motion
+        components
+      * ``radial_velocity`` for any ``d_distance`` component
+      * ``v_{x,y,z}`` for `CartesianDifferential` velocity components
+
+    where ``{lon}`` and ``{lat}`` are the frame names of the angular components.
+
     Parameters
     ----------
     representation : `BaseRepresentation` or None
@@ -591,11 +673,6 @@ class BaseCoordinateFrame(ShapedLikeNDArray, metaclass=FrameMeta):
 
         for repr_diff_cls, mappings in cls._frame_specific_representation_info.items():
 
-            if isinstance(repr_diff_cls, str):
-                # TODO: this provides a layer of backwards compatibility in
-                # case the key is a string, but now we want explicit classes.
-                repr_diff_cls = _get_repr_cls(repr_diff_cls)
-
             # take the 'names' and 'units' tuples from repr_attrs,
             # and then use the RepresentationMapping objects
             # to update as needed for this frame.
diff --git a/astropy/coordinates/builtin_frames/altaz.py b/astropy/coordinates/builtin_frames/altaz.py
index 5a64abc4cd..822339f7bb 100644
--- a/astropy/coordinates/builtin_frames/altaz.py
+++ b/astropy/coordinates/builtin_frames/altaz.py
@@ -100,29 +100,8 @@ class AltAz(BaseCoordinateFrame):
         r.SphericalRepresentation: [
             RepresentationMapping('lon', 'az'),
             RepresentationMapping('lat', 'alt')
-        ],
-        r.SphericalCosLatDifferential: [
-            RepresentationMapping('d_lon_coslat', 'pm_az_cosalt', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_alt', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s),
-        ],
-        r.SphericalDifferential: [
-            RepresentationMapping('d_lon', 'pm_az', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_alt', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s)
-        ],
-        r.CartesianDifferential: [
-            RepresentationMapping('d_x', 'v_x', u.km/u.s),
-            RepresentationMapping('d_y', 'v_y', u.km/u.s),
-            RepresentationMapping('d_z', 'v_z', u.km/u.s),
-        ],
+        ]
     }
-    frame_specific_representation_info[r.UnitSphericalRepresentation] = \
-        frame_specific_representation_info[r.SphericalRepresentation]
-    frame_specific_representation_info[r.UnitSphericalCosLatDifferential] = \
-        frame_specific_representation_info[r.SphericalCosLatDifferential]
-    frame_specific_representation_info[r.UnitSphericalDifferential] = \
-        frame_specific_representation_info[r.SphericalDifferential]
 
     default_representation = r.SphericalRepresentation
     default_differential = r.SphericalCosLatDifferential
diff --git a/astropy/coordinates/builtin_frames/baseradec.py b/astropy/coordinates/builtin_frames/baseradec.py
index 23bb8de3a9..78e31ab8f8 100644
--- a/astropy/coordinates/builtin_frames/baseradec.py
+++ b/astropy/coordinates/builtin_frames/baseradec.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 
-from ... import units as u
 from .. import representation as r
 from ..baseframe import BaseCoordinateFrame, RepresentationMapping
 
@@ -58,29 +57,8 @@ class BaseRADecFrame(BaseCoordinateFrame):
         r.SphericalRepresentation: [
             RepresentationMapping('lon', 'ra'),
             RepresentationMapping('lat', 'dec')
-        ],
-        r.SphericalCosLatDifferential: [
-            RepresentationMapping('d_lon_coslat', 'pm_ra_cosdec', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_dec', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s)
-        ],
-        r.SphericalDifferential: [
-            RepresentationMapping('d_lon', 'pm_ra', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_dec', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s)
-        ],
-        r.CartesianDifferential: [
-            RepresentationMapping('d_x', 'v_x', u.km/u.s),
-            RepresentationMapping('d_y', 'v_y', u.km/u.s),
-            RepresentationMapping('d_z', 'v_z', u.km/u.s)
-        ],
+        ]
     }
-    frame_specific_representation_info[r.UnitSphericalRepresentation] = \
-        frame_specific_representation_info[r.SphericalRepresentation]
-    frame_specific_representation_info[r.UnitSphericalCosLatDifferential] = \
-        frame_specific_representation_info[r.SphericalCosLatDifferential]
-    frame_specific_representation_info[r.UnitSphericalDifferential] = \
-        frame_specific_representation_info[r.SphericalDifferential]
 
     default_representation = r.SphericalRepresentation
     default_differential = r.SphericalCosLatDifferential
diff --git a/astropy/coordinates/builtin_frames/ecliptic.py b/astropy/coordinates/builtin_frames/ecliptic.py
index 7ca6837c09..75fa61eae2 100644
--- a/astropy/coordinates/builtin_frames/ecliptic.py
+++ b/astropy/coordinates/builtin_frames/ecliptic.py
@@ -63,29 +63,6 @@ class BaseEclipticFrame(BaseCoordinateFrame):
     {params}
     """
 
-    frame_specific_representation_info = {
-        r.SphericalCosLatDifferential: [
-            RepresentationMapping('d_lon_coslat', 'pm_lon_coslat', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_lat', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s),
-        ],
-        r.SphericalDifferential: [
-            RepresentationMapping('d_lon', 'pm_lon', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_lat', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s),
-        ],
-        r.CartesianDifferential: [
-            RepresentationMapping('d_x', 'v_x', u.km/u.s),
-            RepresentationMapping('d_y', 'v_y', u.km/u.s),
-            RepresentationMapping('d_z', 'v_z', u.km/u.s),
-        ],
-    }
-
-    frame_specific_representation_info[r.UnitSphericalCosLatDifferential] = \
-        frame_specific_representation_info[r.SphericalCosLatDifferential]
-    frame_specific_representation_info[r.UnitSphericalDifferential] = \
-        frame_specific_representation_info[r.SphericalDifferential]
-
     default_representation = r.SphericalRepresentation
     default_differential = r.SphericalCosLatDifferential
 
diff --git a/astropy/coordinates/builtin_frames/galactic.py b/astropy/coordinates/builtin_frames/galactic.py
index 6969594292..1db236682f 100644
--- a/astropy/coordinates/builtin_frames/galactic.py
+++ b/astropy/coordinates/builtin_frames/galactic.py
@@ -79,24 +79,8 @@ class Galactic(BaseCoordinateFrame):
             RepresentationMapping('d_x', 'U', u.km/u.s),
             RepresentationMapping('d_y', 'V', u.km/u.s),
             RepresentationMapping('d_z', 'W', u.km/u.s)
-        ],
-        r.SphericalCosLatDifferential: [
-            RepresentationMapping('d_lon_coslat', 'pm_l_cosb', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_b', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s),
-        ],
-        r.SphericalDifferential: [
-            RepresentationMapping('d_lon', 'pm_l', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_b', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s),
         ]
     }
-    frame_specific_representation_info[r.UnitSphericalRepresentation] = \
-        frame_specific_representation_info[r.SphericalRepresentation]
-    frame_specific_representation_info[r.UnitSphericalCosLatDifferential] = \
-        frame_specific_representation_info[r.SphericalCosLatDifferential]
-    frame_specific_representation_info[r.UnitSphericalDifferential] = \
-        frame_specific_representation_info[r.SphericalDifferential]
 
     default_representation = r.SphericalRepresentation
     default_differential = r.SphericalCosLatDifferential
diff --git a/astropy/coordinates/builtin_frames/galactocentric.py b/astropy/coordinates/builtin_frames/galactocentric.py
index a3f9d44665..13a887576e 100644
--- a/astropy/coordinates/builtin_frames/galactocentric.py
+++ b/astropy/coordinates/builtin_frames/galactocentric.py
@@ -168,13 +168,6 @@ class Galactocentric(BaseCoordinateFrame):
              ( 289.77285255,  50.06290457,  8.59216010e+01)]>
 
     """
-    frame_specific_representation_info = {
-        r.CartesianDifferential: [
-            RepresentationMapping('d_x', 'v_x', u.km/u.s),
-            RepresentationMapping('d_y', 'v_y', u.km/u.s),
-            RepresentationMapping('d_z', 'v_z', u.km/u.s),
-        ],
-    }
 
     default_representation = r.CartesianRepresentation
     default_differential = r.CartesianDifferential
diff --git a/astropy/coordinates/builtin_frames/lsr.py b/astropy/coordinates/builtin_frames/lsr.py
index a5533d2dd6..d2593bdf93 100644
--- a/astropy/coordinates/builtin_frames/lsr.py
+++ b/astropy/coordinates/builtin_frames/lsr.py
@@ -153,29 +153,8 @@ class GalacticLSR(BaseCoordinateFrame):
         r.SphericalRepresentation: [
             RepresentationMapping('lon', 'l'),
             RepresentationMapping('lat', 'b')
-        ],
-        r.SphericalCosLatDifferential: [
-            RepresentationMapping('d_lon_coslat', 'pm_l_cosb', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_b', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s)
-        ],
-        r.SphericalDifferential: [
-            RepresentationMapping('d_lon', 'pm_l', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_b', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s)
-        ],
-        r.CartesianDifferential: [
-            RepresentationMapping('d_x', 'v_x', u.km/u.s),
-            RepresentationMapping('d_y', 'v_y', u.km/u.s),
-            RepresentationMapping('d_z', 'v_z', u.km/u.s)
-        ],
+        ]
     }
-    frame_specific_representation_info[r.UnitSphericalRepresentation] = \
-        frame_specific_representation_info[r.SphericalRepresentation]
-    frame_specific_representation_info[r.UnitSphericalCosLatDifferential] = \
-        frame_specific_representation_info[r.SphericalCosLatDifferential]
-    frame_specific_representation_info[r.UnitSphericalDifferential] = \
-        frame_specific_representation_info[r.SphericalDifferential]
 
     default_representation = r.SphericalRepresentation
     default_differential = r.SphericalCosLatDifferential
diff --git a/astropy/coordinates/builtin_frames/supergalactic.py b/astropy/coordinates/builtin_frames/supergalactic.py
index 5478ad04da..903cad3156 100644
--- a/astropy/coordinates/builtin_frames/supergalactic.py
+++ b/astropy/coordinates/builtin_frames/supergalactic.py
@@ -59,28 +59,12 @@ class Supergalactic(BaseCoordinateFrame):
             RepresentationMapping('y', 'sgy'),
             RepresentationMapping('z', 'sgz')
         ],
-        r.SphericalCosLatDifferential: [
-            RepresentationMapping('d_lon_coslat', 'pm_sgl_cossgb', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_sgb', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s),
-        ],
-        r.SphericalDifferential: [
-            RepresentationMapping('d_lon', 'pm_sgl', u.mas/u.yr),
-            RepresentationMapping('d_lat', 'pm_sgb', u.mas/u.yr),
-            RepresentationMapping('d_distance', 'radial_velocity', u.km/u.s),
-        ],
         r.CartesianDifferential: [
             RepresentationMapping('d_x', 'v_x', u.km/u.s),
             RepresentationMapping('d_y', 'v_y', u.km/u.s),
             RepresentationMapping('d_z', 'v_z', u.km/u.s)
         ],
     }
-    frame_specific_representation_info[r.UnitSphericalRepresentation] = \
-        frame_specific_representation_info[r.SphericalRepresentation]
-    frame_specific_representation_info[r.UnitSphericalCosLatDifferential] = \
-        frame_specific_representation_info[r.SphericalCosLatDifferential]
-    frame_specific_representation_info[r.UnitSphericalDifferential] = \
-        frame_specific_representation_info[r.SphericalDifferential]
 
     default_representation = r.SphericalRepresentation
     default_differential = r.SphericalCosLatDifferential


Test patch:
diff --git a/astropy/coordinates/tests/test_frames.py b/astropy/coordinates/tests/test_frames.py
index 344d62a763..25e2d46090 100644
--- a/astropy/coordinates/tests/test_frames.py
+++ b/astropy/coordinates/tests/test_frames.py
@@ -254,20 +254,11 @@ def test_converting_units():
 
     # but that *shouldn't* hold if we turn off units for the representation
     class FakeICRS(ICRS):
-        frame_specific_representation_info = {
-            'spherical': {'names': ('ra', 'dec', 'distance'),
-                          'units': (None, None, None)},
-            'unitspherical': {'names': ('ra', 'dec'),
-                              'units': (None, None)}
-        }
-
         frame_specific_representation_info = {
             'spherical': [RepresentationMapping('lon', 'ra', u.hourangle),
                           RepresentationMapping('lat', 'dec', None),
                           RepresentationMapping('distance', 'distance')]  # should fall back to default of None unit
         }
-        frame_specific_representation_info['unitspherical'] = \
-            frame_specific_representation_info['spherical']
 
     fi = FakeICRS(i4.data)
     ri2 = ''.join(rexrepr.split(repr(i2)))
@@ -283,6 +274,74 @@ def test_converting_units():
     assert fi.ra.unit == u.hourangle
 
 
+def test_representation_info():
+    from ..baseframe import RepresentationMapping
+    from ..builtin_frames import ICRS
+
+    class NewICRS1(ICRS):
+        frame_specific_representation_info = {
+            r.SphericalRepresentation: [
+                RepresentationMapping('lon', 'rara', u.hourangle),
+                RepresentationMapping('lat', 'decdec', u.degree),
+                RepresentationMapping('distance', 'distance', u.kpc)]
+        }
+
+    i1 = NewICRS1(rara=10*u.degree, decdec=-12*u.deg, distance=1000*u.pc,
+                  pm_rara_cosdecdec=100*u.mas/u.yr,
+                  pm_decdec=17*u.mas/u.yr,
+                  radial_velocity=10*u.km/u.s)
+    assert allclose(i1.rara, 10*u.deg)
+    assert i1.rara.unit == u.hourangle
+    assert allclose(i1.decdec, -12*u.deg)
+    assert allclose(i1.distance, 1000*u.pc)
+    assert i1.distance.unit == u.kpc
+    assert allclose(i1.pm_rara_cosdecdec, 100*u.mas/u.yr)
+    assert allclose(i1.pm_decdec, 17*u.mas/u.yr)
+
+    # this should auto-set the names of UnitSpherical:
+    i1.set_representation_cls(r.UnitSphericalRepresentation,
+                              s=r.UnitSphericalCosLatDifferential)
+    assert allclose(i1.rara, 10*u.deg)
+    assert allclose(i1.decdec, -12*u.deg)
+    assert allclose(i1.pm_rara_cosdecdec, 100*u.mas/u.yr)
+    assert allclose(i1.pm_decdec, 17*u.mas/u.yr)
+
+    # For backwards compatibility, we also support the string name in the
+    # representation info dictionary:
+    class NewICRS2(ICRS):
+        frame_specific_representation_info = {
+            'spherical': [
+                RepresentationMapping('lon', 'ang1', u.hourangle),
+                RepresentationMapping('lat', 'ang2', u.degree),
+                RepresentationMapping('distance', 'howfar', u.kpc)]
+        }
+
+    i2 = NewICRS2(ang1=10*u.degree, ang2=-12*u.deg, howfar=1000*u.pc)
+    assert allclose(i2.ang1, 10*u.deg)
+    assert i2.ang1.unit == u.hourangle
+    assert allclose(i2.ang2, -12*u.deg)
+    assert allclose(i2.howfar, 1000*u.pc)
+    assert i2.howfar.unit == u.kpc
+
+    # Test that the differential kwargs get overridden
+    class NewICRS3(ICRS):
+        frame_specific_representation_info = {
+            r.SphericalCosLatDifferential: [
+                RepresentationMapping('d_lon_coslat', 'pm_ang1', u.hourangle/u.year),
+                RepresentationMapping('d_lat', 'pm_ang2'),
+                RepresentationMapping('d_distance', 'vlos', u.kpc/u.Myr)]
+        }
+
+    i3 = NewICRS3(lon=10*u.degree, lat=-12*u.deg, distance=1000*u.pc,
+                  pm_ang1=1*u.mas/u.yr, pm_ang2=2*u.mas/u.yr,
+                  vlos=100*u.km/u.s)
+    assert allclose(i3.pm_ang1, 1*u.mas/u.yr)
+    assert i3.pm_ang1.unit == u.hourangle/u.year
+    assert allclose(i3.pm_ang2, 2*u.mas/u.yr)
+    assert allclose(i3.vlos, 100*u.km/u.s)
+    assert i3.vlos.unit == u.kpc/u.Myr
+
+
 def test_realizing():
     from ..builtin_frames import ICRS, FK5
     from ...time import Time