Is it possible to add a documentation string to a namedtuple in an easy manner?
Yes, in several ways.
Subclass typing.NamedTuple - Python 3.6+
As of Python 3.6 we can use a class definition with typing.NamedTuple directly, with a docstring (and annotations!):
from typing import NamedTuple
class Card(NamedTuple):
    """This is a card type."""
    suit: str
    rank: str
Compared to Python 2, declaring empty __slots__ is not necessary. In Python 3.8, it isn't necessary even for subclasses.
Note that declaring __slots__ cannot be non-empty!
In Python 3, you can also easily alter the doc on a namedtuple:
NT = collections.namedtuple('NT', 'foo bar')
NT.__doc__ = """:param str foo: foo name
:param list bar: List of bars to bar"""
Which allows us to view the intent for them when we call help on them:
Help on class NT in module __main__:
class NT(builtins.tuple)
 |  :param str foo: foo name
 |  :param list bar: List of bars to bar
...
This is really straightforward compared to the difficulties we have accomplishing the same thing in Python 2.
Python 2
In Python 2, you'll need to
- subclass the namedtuple, and 
- declare __slots__ == ()
Declaring __slots__ is an important part that the other answers here miss . 
If you don't declare __slots__ - you could add mutable ad-hoc attributes to the instances, introducing bugs.
class Foo(namedtuple('Foo', 'bar')):
    """no __slots__ = ()!!!"""
And now:
>>> f = Foo('bar')
>>> f.bar
'bar'
>>> f.baz = 'what?'
>>> f.__dict__
{'baz': 'what?'}
Each instance will create a separate __dict__ when __dict__ is accessed (the lack of __slots__ won't otherwise impede the functionality, but the lightweightness of the tuple, immutability, and declared attributes are all important features of namedtuples). 
You'll also want a __repr__, if you want what is echoed on the command line to give you an equivalent object:
NTBase = collections.namedtuple('NTBase', 'foo bar')
class NT(NTBase):
    """
    Individual foo bar, a namedtuple
    :param str foo: foo name
    :param list bar: List of bars to bar
    """
    __slots__ = ()
a __repr__ like this is needed if you create the base namedtuple with a different name (like we did above with the name string argument, 'NTBase'):
    def __repr__(self):
        return 'NT(foo={0}, bar={1})'.format(
                repr(self.foo), repr(self.bar))
To test the repr, instantiate, then test for equality of a pass to eval(repr(instance))
nt = NT('foo', 'bar')
assert eval(repr(nt)) == nt
Example from the documentation
The docs also give such an example, regarding __slots__ - I'm adding my own docstring to it:
class Point(namedtuple('Point', 'x y')):
    """Docstring added here, not in original"""
    __slots__ = ()
    @property
    def hypot(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
        return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)
...
The subclass shown above sets __slots__ to an empty tuple. This helps
  keep memory requirements low by preventing the creation of instance
  dictionaries.
This demonstrates in-place usage (like another answer here suggests), but note that the in-place usage may become confusing when you look at the method resolution order, if you're debugging, which is why I originally suggested using Base as a suffix for the base namedtuple:
>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
                # ^^^^^---------------------^^^^^-- same names!        
To prevent creation of a __dict__ when subclassing from a class that uses it, you must also declare it in the subclass. See also this answer for more caveats on using __slots__.