Repository: astropy/astropy

Issue:
Table indexing cannot be used to update a table
If I have an index on a column, I can use it to retrieve a list of rows with `.loc`, but then it seems there is no way to update the corresponding rows in the source table:
```python
In [1]: %astropy
Numpy 1.12.0
Astropy 2.0.dev18049

In [2]: t = Table([list('abcde'), list(range(5))])

In [3]: t
Out[3]: 
<Table length=5>
col0  col1
str1 int64
---- -----
   a     0
   b     1
   c     2
   d     3
   e     4

In [4]: t.add_index('col0')

In [5]: t.loc[['a', 'c']]
Out[5]: 
<Table length=2>
col0  col1
str1 int64
---- -----
   a     0
   c     2

In [6]: t.loc[['a', 'c']]['col1'] = 10

In [7]: t
Out[7]: 
<Table length=5>
col0  col1
str1 int64
---- -----
   a     0
   b     1
   c     2
   d     3
   e     4
```
I can understand this because it uses Numpy's fancy indexing which makes a copy, but then something that would be useful is a way to get the list of indices of these rows in the source table. It is computed (https://github.com/astropy/astropy/blob/master/astropy/table/index.py#L818) but currently it's not possible to retrieve this. Maybe with a `TableLoc.get_indices` method or something similar ?
ping @taldcroft

Discussion:
astropy-bot[bot]: Hi there @sushobhana :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:.

I noticed the following issues with this pull request:

* The milestone has not been set (this can only be set by a maintainer)
* Changelog entry not present (or pull request number missing) and neither the **Affects-dev** nor the **no-changelog-entry-needed** label are set

Would it be possible to fix these? Thanks!

*If there are any issues with this message, please report them [here](https://github.com/astropy/astropy-bot/issues).*
sushobhana: sorry!the last commit was a mistake!
sushobhana: @taldcroft Needed some help on writing tests! i have written some for the `loc_indices`.Have a look !
sushobhana: @taldcroft anything else that needs to be changed here?
taldcroft: @sushobhana - this needs to be rebased to fix a conflict in CHANGES.rst.  (Do this using command line git, not the github Resolve Conflicts button).

@saimn - are you good with this now?
sushobhana: @taldcroft i did `git pull --rebase` from the astropy/master and i think i made a mistake..it is now showing all the commits made by others. is that an issue? i am new to this workflow .. what should i do now?Thanks for your time!
sushobhana: @taldcroft Thanks for responding and accepting it!
Should i ` git pull astropy/master --rebase` to manage the conflict?
saimn: @sushobhana - did you manage to fix your rebase ? It seems that the PR is still not mergeable, and you still have changelog lines not related to this PR.
sushobhana: @saimn i am not able to rebase it! i would be grateful if you could guide me!
saimn: Did you follow the steps like described in http://docs.astropy.org/en/latest/development/workflow/development_workflow.html#how-to-rebase ?
You have to fetch the astropy remote, and then rebase on it (the actual commands depend on the names of your remotes).
sushobhana: @saimn Thank You!
kindly see if any further changes are required?
saimn: @sushobhana - I left mostly stylistic comments, but 
saimn: Sorry for the previous unfinished comment.

@sushobhana - I left mostly stylistic comments, but I also lost twice some comments because you are still editing the last commit ... If you think this is ready, please stop editing the commits, and use new commits instead to do the changes. At the end you will maybe have to squash the commits, but only once we are done with the changes.
sushobhana: @saimn anything else?
saimn: Thanks @sushobhana , looks good now ! Last things that would be appreciated is to [squash your commits](http://docs.astropy.org/en/latest/development/workflow/development_workflow.html#how-to-squash), which allows to simplify the history by removing the trial and error commits.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index e59f386808..ea6b620909 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -55,9 +55,6 @@ astropy.io.fits
   in particular for cases where the tables contain one or more string columns
   and when done through ``Table.read``. [#6821]
 
-- The performance for writing tables from ``Table.write`` has now been
-  significantly improved for tables containing one or more string columns. [#6920]
-
 - The ``Table.read`` now supports a ``memmap=`` keyword argument to control
   whether or not to use  memory mapping when reading the table. [#6821]
 
@@ -67,12 +64,6 @@ astropy.io.fits
   the same columns are decoded to Unicode strings (Numpy type U) which uses more
   memory. [#6821]
 
-- The ``table_to_hdu`` function and the ``BinTableHDU.from_columns`` and
-  ``FITS_rec.from_columns`` methods now include a ``character_as_bytes``
-  keyword argument - if set to `True`, then when string columns are accessed,
-  byte columns will be returned, which can provide significantly improved
-  performance. [#6920]
-
 astropy.io.misc
 ^^^^^^^^^^^^^^^
 
@@ -131,6 +122,11 @@ astropy.table
 - Added a new ``showtable`` command-line script to view binary or ASCII table
   files. [#6859]
 
+- Added new table property ``astropy.table.Table.loc_indices`` that returns the
+  location of rows by indexes. [#6831]
+
+- Allow updating of table by indices through the property ``astropy.table.Table.loc``. [#6831]
+
 astropy.tests
 ^^^^^^^^^^^^^
 
diff --git a/astropy/io/fits/connect.py b/astropy/io/fits/connect.py
index a71fb4605d..da0877a89f 100644
--- a/astropy/io/fits/connect.py
+++ b/astropy/io/fits/connect.py
@@ -236,7 +236,7 @@ def write_table_fits(input, output, overwrite=False):
         Whether to overwrite any existing file without warning.
     """
 
-    table_hdu = table_to_hdu(input, character_as_bytes=True)
+    table_hdu = table_to_hdu(input)
 
     # Check if output file already exists
     if isinstance(output, str) and os.path.exists(output):
diff --git a/astropy/io/fits/convenience.py b/astropy/io/fits/convenience.py
index a2102f74e2..4c011ccd77 100644
--- a/astropy/io/fits/convenience.py
+++ b/astropy/io/fits/convenience.py
@@ -423,7 +423,7 @@ def writeto(filename, data, header=None, output_verify='exception',
                 checksum=checksum)
 
 
-def table_to_hdu(table, character_as_bytes=False):
+def table_to_hdu(table):
     """
     Convert an `~astropy.table.Table` object to a FITS
     `~astropy.io.fits.BinTableHDU`.
@@ -432,10 +432,6 @@ def table_to_hdu(table, character_as_bytes=False):
     ----------
     table : astropy.table.Table
         The table to convert.
-    character_as_bytes : bool
-        Whether to return bytes for string columns when accessed from the HDU.
-        By default this is `False` and (unicode) strings are returned, but for
-        large tables this may use up a lot of memory.
 
     Returns
     -------
@@ -478,7 +474,7 @@ def table_to_hdu(table, character_as_bytes=False):
 
         # TODO: it might be better to construct the FITS table directly from
         # the Table columns, rather than go via a structured array.
-        table_hdu = BinTableHDU.from_columns(np.array(table.filled()), header=hdr, character_as_bytes=True)
+        table_hdu = BinTableHDU.from_columns(np.array(table.filled()), header=hdr)
         for col in table_hdu.columns:
             # Binary FITS tables support TNULL *only* for integer data columns
             # TODO: Determine a schema for handling non-integer masked columns
@@ -495,7 +491,7 @@ def table_to_hdu(table, character_as_bytes=False):
 
             col.null = fill_value.astype(table[col.name].dtype)
     else:
-        table_hdu = BinTableHDU.from_columns(np.array(table.filled()), header=hdr, character_as_bytes=character_as_bytes)
+        table_hdu = BinTableHDU.from_columns(np.array(table.filled()), header=hdr)
 
     # Set units for output HDU
     for col in table_hdu.columns:
diff --git a/astropy/io/fits/fitsrec.py b/astropy/io/fits/fitsrec.py
index 574b4073b1..1358607ea0 100644
--- a/astropy/io/fits/fitsrec.py
+++ b/astropy/io/fits/fitsrec.py
@@ -15,7 +15,7 @@ from numpy import char as chararray
 from .column import (ASCIITNULL, FITS2NUMPY, ASCII2NUMPY, ASCII2STR, ColDefs,
                      _AsciiColDefs, _FormatX, _FormatP, _VLF, _get_index,
                      _wrapx, _unwrapx, _makep, Delayed)
-from .util import decode_ascii, encode_ascii, _rstrip_inplace
+from .util import decode_ascii, encode_ascii
 from ...utils import lazyproperty
 
 
@@ -267,7 +267,7 @@ class FITS_rec(np.recarray):
         self._uint = False
 
     @classmethod
-    def from_columns(cls, columns, nrows=0, fill=False, character_as_bytes=False):
+    def from_columns(cls, columns, nrows=0, fill=False):
         """
         Given a `ColDefs` object of unknown origin, initialize a new `FITS_rec`
         object.
@@ -329,7 +329,6 @@ class FITS_rec(np.recarray):
         raw_data = np.empty(columns.dtype.itemsize * nrows, dtype=np.uint8)
         raw_data.fill(ord(columns._padding_byte))
         data = np.recarray(nrows, dtype=columns.dtype, buf=raw_data).view(cls)
-        data._character_as_bytes = character_as_bytes
 
         # Make sure the data is a listener for changes to the columns
         columns._add_listener(data)
@@ -1281,6 +1280,21 @@ def _get_recarray_field(array, key):
     return field
 
 
+def _rstrip_inplace(array, chars=None):
+    """
+    Performs an in-place rstrip operation on string arrays.
+    This is necessary since the built-in `np.char.rstrip` in Numpy does not
+    perform an in-place calculation.  This can be removed if ever
+    https://github.com/numpy/numpy/issues/6303 is implemented (however, for
+    the purposes of this module the only in-place vectorized string functions
+    we need are rstrip and encode).
+    """
+
+    for item in np.nditer(array, flags=['zerosize_ok'],
+                                 op_flags=['readwrite']):
+        item[...] = item.item().rstrip(chars)
+
+
 class _UnicodeArrayEncodeError(UnicodeEncodeError):
     def __init__(self, encoding, object_, start, end, reason, index):
         super().__init__(encoding, object_, start, end, reason)
diff --git a/astropy/io/fits/hdu/table.py b/astropy/io/fits/hdu/table.py
index a869994a11..fddd73ad9c 100644
--- a/astropy/io/fits/hdu/table.py
+++ b/astropy/io/fits/hdu/table.py
@@ -71,7 +71,7 @@ class _TableLikeHDU(_ValidHDU):
 
     @classmethod
     def from_columns(cls, columns, header=None, nrows=0, fill=False,
-                     character_as_bytes=False, **kwargs):
+                     **kwargs):
         """
         Given either a `ColDefs` object, a sequence of `Column` objects,
         or another table HDU or table data (a `FITS_rec` or multi-field
@@ -111,11 +111,6 @@ class _TableLikeHDU(_ValidHDU):
             copy the data from input, undefined cells will still be filled with
             zeros/blanks.
 
-        character_as_bytes : bool
-            Whether to return bytes for string columns when accessed from the
-            HDU. By default this is `False` and (unicode) strings are returned,
-            but for large tables this may use up a lot of memory.
-
         Notes
         -----
 
@@ -124,9 +119,8 @@ class _TableLikeHDU(_ValidHDU):
         """
 
         coldefs = cls._columns_type(columns)
-        data = FITS_rec.from_columns(coldefs, nrows=nrows, fill=fill,
-                                     character_as_bytes=character_as_bytes)
-        hdu = cls(data=data, header=header, character_as_bytes=character_as_bytes, **kwargs)
+        data = FITS_rec.from_columns(coldefs, nrows=nrows, fill=fill)
+        hdu = cls(data=data, header=header, **kwargs)
         coldefs._add_listener(hdu)
         return hdu
 
diff --git a/astropy/io/fits/util.py b/astropy/io/fits/util.py
index 9899b21211..052332b380 100644
--- a/astropy/io/fits/util.py
+++ b/astropy/io/fits/util.py
@@ -877,53 +877,3 @@ def get_testdata_filepath(filename):
     """
     return data.get_pkg_data_filename(
         'io/fits/tests/data/{}'.format(filename), 'astropy')
-
-
-def _rstrip_inplace(array):
-    """
-    Performs an in-place rstrip operation on string arrays. This is necessary
-    since the built-in `np.char.rstrip` in Numpy does not perform an in-place
-    calculation.
-    """
-
-    # The following implementation convert the string to unsigned integers of
-    # the right length. Trailing spaces (which are represented as 32) are then
-    # converted to null characters (represented as zeros). To avoid creating
-    # large temporary mask arrays, we loop over chunks (attempting to do that
-    # on a 1-D version of the array; large memory may still be needed in the
-    # unlikely case that a string array has small first dimension and cannot
-    # be represented as a contiguous 1-D array in memory).
-
-    dt = array.dtype
-
-    if dt.kind not in 'SU':
-        raise TypeError("This function can only be used on string arrays")
-    # View the array as appropriate integers. The last dimension will
-    # equal the number of characters in each string.
-    bpc = 1 if dt.kind == 'S' else 4
-    dt_int = "{0}{1}u{2}".format(dt.itemsize // bpc, dt.byteorder, bpc)
-    b = array.view(dt_int, np.ndarray)
-    # For optimal speed, work in chunks of the internal ufunc buffer size.
-    bufsize = np.getbufsize()
-    # Attempt to have the strings as a 1-D array to give the chunk known size.
-    # Note: the code will work if this fails; the chunks will just be larger.
-    if b.ndim > 2:
-        try:
-            b.shape = -1, b.shape[-1]
-        except AttributeError:  # can occur for non-contiguous arrays
-            pass
-    for j in range(0, b.shape[0], bufsize):
-        c = b[j:j + bufsize]
-        # Mask which will tell whether we're in a sequence of trailing spaces.
-        mask = np.ones(c.shape[:-1], dtype=bool)
-        # Loop over the characters in the strings, in reverse order. We process
-        # the i-th character of all strings in the chunk at the same time. If
-        # the character is 32, this corresponds to a space, and we then change
-        # this to 0. We then construct a new mask to find rows where the
-        # i-th character is 0 (null) and the i-1-th is 32 (space) and repeat.
-        for i in range(-1, -c.shape[-1], -1):
-            mask &= c[..., i] == 32
-            c[..., i][mask] = 0
-            mask = c[..., i] == 0
-
-    return array
diff --git a/astropy/table/index.py b/astropy/table/index.py
index 52b77593f0..20328c6b89 100644
--- a/astropy/table/index.py
+++ b/astropy/table/index.py
@@ -765,7 +765,7 @@ class TableIndices(list):
 
 
 class TableLoc:
-    '''
+    """
     A pseudo-list of Table rows allowing for retrieval
     of rows by indexed column values.
 
@@ -773,7 +773,7 @@ class TableLoc:
     ----------
     table : Table
         Indexed table to use
-    '''
+    """
 
     def __init__(self, table):
         self.table = table
@@ -781,19 +781,10 @@ class TableLoc:
         if len(self.indices) == 0:
             raise ValueError("Cannot create TableLoc object with no indices")
 
-    def __getitem__(self, item):
-        '''
-        Retrieve Table rows by value slice.
-
-        Parameters
-        ----------
-        item : column element, list, ndarray, slice or tuple
-            Can be a value of the table primary index, a list/ndarray
-            of such values, or a value slice (both endpoints are included).
-            If a tuple is provided, the first element must be
-            an index to use instead of the primary key, and the
-            second element must be as above.
-        '''
+    def _get_rows(self, item):
+        """
+        Retrieve Table rows indexes by value slice.
+        """
 
         if isinstance(item, tuple):
             key, item = item
@@ -815,7 +806,27 @@ class TableLoc:
             # item should be a list or ndarray of values
             rows = []
             for key in item:
-                rows.extend(index.find((key,)))
+                p = index.find((key,))
+                if len(p) == 0:
+                    raise KeyError('No matches found for key {0}'.format(key))
+                else:
+                    rows.extend(p)
+        return rows
+
+    def __getitem__(self, item):
+        """
+        Retrieve Table rows by value slice.
+
+        Parameters
+        ----------
+        item : column element, list, ndarray, slice or tuple
+            Can be a value of the table primary index, a list/ndarray
+            of such values, or a value slice (both endpoints are included).
+            If a tuple is provided, the first element must be
+            an index to use instead of the primary key, and the
+            second element must be as above.
+        """
+        rows = self._get_rows(item)
 
         if len(rows) == 0:  # no matches found
             raise KeyError('No matches found for key {0}'.format(item))
@@ -823,6 +834,57 @@ class TableLoc:
             return self.table[rows[0]]
         return self.table[rows]
 
+    def __setitem__(self, key, value):
+        """
+        Assign Table row's by value slice.
+
+        Parameters
+        ----------
+        key : column element, list, ndarray, slice or tuple
+              Can be a value of the table primary index, a list/ndarray
+              of such values, or a value slice (both endpoints are included).
+              If a tuple is provided, the first element must be
+              an index to use instead of the primary key, and the
+              second element must be as above.
+
+        value : New values of the row elements.
+                Can be a list of tuples/lists to update the row.
+        """
+        rows = self._get_rows(key)
+        if len(rows) == 0:  # no matches found
+            raise KeyError('No matches found for key {0}'.format(key))
+        elif len(rows) == 1:  # single row
+            self.table[rows[0]] = value
+        else:  # multiple rows
+            if len(rows) == len(value):
+                for row, val in zip(rows, value):
+                    self.table[row] = val
+            else:
+                raise ValueError('Right side should contain {0} values'.format(len(rows)))
+
+
+class TableLocIndices(TableLoc):
+
+    def __getitem__(self, item):
+        """
+        Retrieve Table row's indices by value slice.
+
+        Parameters
+        ----------
+        item : column element, list, ndarray, slice or tuple
+               Can be a value of the table primary index, a list/ndarray
+               of such values, or a value slice (both endpoints are included).
+               If a tuple is provided, the first element must be
+               an index to use instead of the primary key, and the
+               second element must be as above.
+        """
+        rows = self._get_rows(item)
+        if len(rows) == 0:  # no matches found
+            raise KeyError('No matches found for key {0}'.format(item))
+        elif len(rows) == 1:  # single row
+            return rows[0]
+        return rows
+
 
 class TableILoc(TableLoc):
     '''
diff --git a/astropy/table/table.py b/astropy/table/table.py
index 934e538494..38d6dbf19e 100644
--- a/astropy/table/table.py
+++ b/astropy/table/table.py
@@ -1,5 +1,5 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
-from .index import TableIndices, TableLoc, TableILoc
+from .index import TableIndices, TableLoc, TableILoc, TableLocIndices
 
 import re
 import sys
@@ -493,6 +493,14 @@ class Table:
         '''
         return TableLoc(self)
 
+    @property
+    def loc_indices(self):
+        """
+        Return a TableLocIndices object that can be used for retrieving
+        the row indices corresponding to given table index key value or values.
+        """
+        return TableLocIndices(self)
+
     @property
     def iloc(self):
         '''
diff --git a/docs/table/indexing.rst b/docs/table/indexing.rst
index 16db28fa47..af6f6c492a 100644
--- a/docs/table/indexing.rst
+++ b/docs/table/indexing.rst
@@ -208,6 +208,59 @@ or table is copied::
   ...    print(t2.indices)
   []
 
+Updating row using indices
+==========================
+
+Row updation can be accomplished by assigning the table property: `~astropy.table.Table.loc` a complete row or a list of rows::
+
+   >>> t = Table([('w', 'x', 'y', 'z'), (10, 1, 9, 9)], names=('a', 'b'), dtype=['str', 'i8'])
+   >>> t.add_index('a')
+   >>> t.loc['x']
+   <Row index=1>
+    a     b
+   str1 int64
+   ---- -----
+      x     1
+   >>> t.loc['x'] = ['a', 12]
+   >>> t
+   <Table length=4>
+    a     b
+   str1 int64
+   ---- -----
+      w    10
+      a    12
+      y     9
+      z     9
+   >>> t.loc[['w', 'y']]
+   <Table length=2>
+    a     b
+   str1 int64
+   ---- -----
+      w    10
+      y     9
+   >>> t.loc[['w', 'z']] = [['b',23], ['c',56]]
+   >>> t
+   <Table length=4>
+    a     b
+   str1 int64
+   ---- -----
+      b    23
+      a    12
+      y     9
+      c    56
+
+Retrieving the location of rows using indices
+=============================================
+
+Retrieval of the location of rows can be accomplished using a table property: `~astropy.table.Table.loc_indices`.
+The `~astropy.table.Table.loc_indices` property can be indexed either by column value, range of
+column values (*including* the bounds), or a list or ndarray of column values::
+
+   >>> t = Table([('w', 'x', 'y', 'z'), (10, 1, 9, 9)], names=('a', 'b'), dtype=['str', 'i8'])
+   >>> t.add_index('a')
+   >>> t.loc_indices['x']
+   1
+
 Engines
 =======
 When creating an index via |add_index|, the keyword argument "engine" may be


Test patch:
diff --git a/astropy/io/fits/tests/test_util.py b/astropy/io/fits/tests/test_util.py
index 09e1dea9e0..4ec3b81c2e 100644
--- a/astropy/io/fits/tests/test_util.py
+++ b/astropy/io/fits/tests/test_util.py
@@ -7,7 +7,6 @@ import gzip
 
 import pytest
 import numpy as np
-from numpy.testing import assert_equal
 
 try:
     from PIL import Image
@@ -17,7 +16,7 @@ except ImportError:
 
 from ....tests.helper import catch_warnings
 from .. import util
-from ..util import ignore_sigint, _rstrip_inplace
+from ..util import ignore_sigint
 from .._numpy_hacks import realign_dtype
 
 from . import FitsTestCase
@@ -149,37 +148,3 @@ class TestUtilMode(FitsTestCase):
             filename = self.temp('test3{0}.dat'.format(num))
             with open(filename, mode) as fileobj:
                 assert util.fileobj_mode(fileobj) == res
-
-
-def test_rstrip_inplace():
-
-    # Incorrect type
-    s = np.array([1, 2, 3])
-    with pytest.raises(TypeError) as exc:
-        _rstrip_inplace(s)
-    assert exc.value.args[0] == 'This function can only be used on string arrays'
-
-    # Bytes array
-    s = np.array(['a ', ' b', ' c c   '], dtype='S6')
-    _rstrip_inplace(s)
-    assert_equal(s, np.array(['a', ' b', ' c c'], dtype='S6'))
-
-    # Unicode array
-    s = np.array(['a ', ' b', ' c c   '], dtype='U6')
-    _rstrip_inplace(s)
-    assert_equal(s, np.array(['a', ' b', ' c c'], dtype='U6'))
-
-    # 2-dimensional array
-    s = np.array([['a ', ' b'], [' c c   ', ' a ']], dtype='S6')
-    _rstrip_inplace(s)
-    assert_equal(s, np.array([['a', ' b'], [' c c', ' a']], dtype='S6'))
-
-    # 3-dimensional array
-    s = np.repeat(' a a ', 24).reshape((2, 3, 4))
-    _rstrip_inplace(s)
-    assert_equal(s, ' a a')
-
-    # 3-dimensional non-contiguous array
-    s = np.repeat(' a a ', 1000).reshape((10, 10, 10))[:2, :3, :4]
-    _rstrip_inplace(s)
-    assert_equal(s, ' a a')
diff --git a/astropy/table/tests/test_index.py b/astropy/table/tests/test_index.py
index 969edffbe3..8fd5dc7de2 100644
--- a/astropy/table/tests/test_index.py
+++ b/astropy/table/tests/test_index.py
@@ -6,7 +6,7 @@ import numpy as np
 from .test_table import SetupData
 from ..bst import BST, FastRBT, FastBST
 from ..sorted_array import SortedArray
-from ..table import QTable, Row
+from ..table import QTable, Row, Table
 from ... import units as u
 from ...time import Time
 from ..column import BaseColumn
@@ -418,14 +418,32 @@ class TestIndex(SetupData):
         for t2 in (t.loc[:], t.iloc[:]):
             assert_col_equal(t2['a'], [1, 2, 3, 4, 5])
 
+    def test_table_loc_indices(self, main_col, table_types, engine):
+        self._setup(main_col, table_types)
+        t = self.t
+
+        t.add_index('a', engine=engine)
+        t.add_index('b', engine=engine)
+
+        t2 = t.loc_indices[self.make_val(3)]  # single label, with primary key 'a'
+        assert t2 == 2
+
+        # list search
+        t2 = t.loc_indices[[self.make_val(1), self.make_val(4), self.make_val(2)]]
+        for i, p in zip(t2,[1,4,2]):  # same order as input list
+            assert i == p-1
+
     def test_invalid_search(self, main_col, table_types, engine):
-        # using .loc with a value not present should raise an exception
+        # using .loc and .loc_indices with a value not present should raise an exception
         self._setup(main_col, table_types)
         t = self.t
 
         t.add_index('a')
         with pytest.raises(KeyError):
             t.loc[self.make_val(6)]
+        with pytest.raises(KeyError):
+            t.loc_indices[self.make_val(6)]
+
 
     def test_copy_index_references(self, main_col, table_types, engine):
         # check against a bug in which indices were given an incorrect
@@ -460,3 +478,35 @@ class TestIndex(SetupData):
             for index, indexp in zip(t.indices, tp.indices):
                 assert np.all(index.data.data == indexp.data.data)
                 assert index.data.data.colnames == indexp.data.data.colnames
+
+    def test_updating_row_byindex(self, main_col, table_types, engine):
+        self._setup(main_col, table_types)
+        t = Table([['a', 'b', 'c', 'd'], [2, 3, 4, 5], [3, 4, 5, 6]], names=('a', 'b', 'c'), meta={'name': 'first table'})
+
+        t.add_index('a', engine=engine)
+        t.add_index('b', engine=engine)
+
+        t.loc['c'] = ['g', 40, 50] # single label, with primary key 'a'
+        t2 = t[2]
+        assert list(t2) == ['g', 40, 50]
+
+        # list search
+        t.loc[['a', 'd', 'b']] = [['a', 20, 30], ['d', 50, 60], ['b', 30, 40]]
+        t2 = [['a', 20, 30], ['d', 50, 60], ['b', 30, 40]]
+        for i, p in zip(t2, [1, 4, 2]):  # same order as input list
+            assert list(t[p-1]) == i
+
+    def test_invalid_updation(self, main_col, table_types, engine):
+        # using .loc and .loc_indices with a value not present should raise an exception
+        self._setup(main_col, table_types)
+        t = Table([[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]], names=('a', 'b', 'c'), meta={'name': 'first table'})
+
+        t.add_index('a')
+        with pytest.raises(ValueError):
+            t.loc[3] = [[1,2,3]]
+        with pytest.raises(ValueError):
+            t.loc[[1, 4, 2]] = [[1, 2, 3], [4, 5, 6]]
+        with pytest.raises(ValueError):
+            t.loc[[1, 4, 2]] = [[1, 2, 3], [4, 5, 6], [2, 3]]
+        with pytest.raises(ValueError):
+            t.loc[[1, 4, 2]] = [[1, 2, 3], [4, 5], [2, 3]]