240

Here is my code:

import pandas as pd

data = pd.DataFrame({'Odd':[1,3,5,6,7,9], 'Even':[0,2,4,6,8,10]})

for i in reversed(data):
    print(data['Odd'], data['Even'])

When I run this code, i get the following error:

Traceback (most recent call last):
  File "C:\Python33\lib\site-packages\pandas\core\generic.py", line 665, in _get_item_cache
    return cache[item]
KeyError: 5

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\*****\Documents\******\********\****.py", line 5, in <module>
    for i in reversed(data):
  File "C:\Python33\lib\site-packages\pandas\core\frame.py", line 2003, in __getitem__
    return self._get_item_cache(key)
  File "C:\Python33\lib\site-packages\pandas\core\generic.py", line 667, in _get_item_cache
    values = self._data.get(item)
  File "C:\Python33\lib\site-packages\pandas\core\internals.py", line 1656, in get
    _, block = self._find_block(item)
  File "C:\Python33\lib\site-packages\pandas\core\internals.py", line 1936, in _find_block
    self._check_have(item)
  File "C:\Python33\lib\site-packages\pandas\core\internals.py", line 1943, in _check_have
    raise KeyError('no item named %s' % com.pprint_thing(item))
KeyError: 'no item named 5'

Why am I getting this error?
How can I fix that?
What is the right way to reverse pandas.DataFrame?

cs95
  • 379,657
  • 97
  • 704
  • 746
Michael
  • 15,386
  • 36
  • 94
  • 143
  • 1
    You still haven't given an example of the output you want. I know how to get around the fact `reversed(data)` doesn't work, but I don't know why you would want to print the whole `Odd` and `Even` columns once for each column in the frame, which is what your code would do if you used `reversed(list(data))`. – DSM Dec 07 '13 at 17:22
  • I want to start for loop from the end of my dataFrame – Michael Dec 07 '13 at 17:23
  • 3
    Then I think your question is a dup of [this one](http://stackoverflow.com/questions/16140174/iterating-through-dataframe-row-index-in-reverse-order), and you want something like `for i, row in data[::-1].iterrows(): print row["Odd"], row["Even"]`. Please always give examples in your question of the output you expect; it makes life much easier on everyone. – DSM Dec 07 '13 at 17:31

7 Answers7

444
data.reindex(index=data.index[::-1])

or simply:

data.iloc[::-1]

will reverse your data frame, if you want to have a for loop which goes from down to up you may do:

for idx in reversed(data.index):
    print(idx, data.loc[idx, 'Even'], data.loc[idx, 'Odd'])

or

for idx in reversed(data.index):
    print(idx, data.Even[idx], data.Odd[idx])

You are getting an error because reversed first calls data.__len__() which returns 6. Then it tries to call data[j - 1] for j in range(6, 0, -1), and the first call would be data[5]; but in pandas dataframe data[5] means column 5, and there is no column 5 so it will throw an exception. ( see docs )

behzad.nouri
  • 74,723
  • 18
  • 126
  • 124
  • 1
    if you are having problems you can try this: `for index, row in df.iloc[::-1].iterrows():` – kristian Dec 05 '16 at 23:39
  • 2
    any way to do it **in place**? equivalent of hypothetical `data.reindex(index=data.index[::-1], inplace=True)` – NeuronQ Jul 12 '17 at 10:09
  • 6
    Can do `data = data.reindex(index=data.index[::-1])` then `data.reset_index(inplace=True, drop=True)` and it will be reset in place. – Matts Jul 21 '18 at 01:45
  • 14
    Is `df = df[::-1]` a pythonic and valid solution? – tommy.carstensen Oct 06 '18 at 23:47
  • 5
    @tommy.carstensen yes, and it should be the top answer – rosstripi Oct 24 '18 at 18:21
  • 5
    @tommy.carstensen yes it is, more so than this solution and is also faster. See rationale and benchmarks [here](https://stackoverflow.com/a/65391420/4909087). – cs95 Jan 17 '21 at 18:02
129

You can reverse the rows in an even simpler way:

df[::-1]
Asclepius
  • 57,944
  • 17
  • 167
  • 143
user1951
  • 1,411
  • 1
  • 10
  • 8
  • 5
    I like to define my own `reverse()` method with `pd.Series.reverse = pd.DataFrame.reverse = lambda self: self[::-1]` because it looks nicer when chaining methods, e.g. `df.reverse().iterrows()`. – Ben Mares Feb 19 '19 at 17:54
  • 1
    @BenMares great but the `iterrows()` makes it looks less nice :-( – cs95 Dec 21 '20 at 04:42
  • The `iterrows()` is not in general necessary. It was only an example, in the case one wants to do `for row in `... It suffices to do `df.reverse()` alone to get the reversed dataframe. – Ben Mares Dec 21 '20 at 07:59
  • 2
    `df[::-1]` should be the best method for this, this outperforms other answers on this page by far, see [here](https://stackoverflow.com/a/65391420/4909087) for timings. – cs95 Dec 21 '20 at 10:51
  • What is this method called? I would like to read up on how it works exactly (unless someone wants to write that here, of course!) – Espen Sales Oct 24 '21 at 17:26
66

What is the right way to reverse a pandas DataFrame?

TL;DR: df[::-1]

This is the best method for reversing a DataFrame, because 1) it is constant run time, i.e. O(1) 2) it's a single operation, and 3) concise/readable (assuming familiarity with slice notation).


Long Version

I've found the ol' slicing trick df[::-1] (or the equivalent df.loc[::-1]1) to be the most concise and idiomatic way of reversing a DataFrame. This mirrors the python list reversal syntax lst[::-1] and is clear in its intent. With the loc syntax, you are also able to slice columns if required, so it is a bit more flexible.

Some points to consider while handling the index:

  • "what if I want to reverse the index as well?"

    • you're already done. df[::-1] reverses both the index and values.
  • "what if I want to drop the index from the result?"

  • "what if I want to keep the index untouched (IOW, only reverse the data, not the index)?"

    • this is somewhat unconventional because it implies the index isn't really relevant to the data. Perhaps consider removing it entirely? Although what you're asking for can technically be achieved using either df[:] = df[::-1] which creates an in-place update to df, or df.loc[::-1].set_index(df.index), which returns a copy.

1: df.loc[::-1] and df.iloc[::-1] are equivalent since the slicing syntax remains the same, whether you're reversing by position (iloc) or label (loc).


The Proof is in the Pudding

enter image description here

X-axis represents the dataset size. Y-axis represents time taken to reverse. No method scales as well as the slicing trick, it's all the way at the bottom of the graph. Benchmarking code for reference, plots generated using perfplot.


Comments on other solutions

  • df.reindex(index=df.index[::-1]) is clearly a popular solution, but on first glance, how obvious is it to an unfamiliar reader that this code is "reversing a DataFrame"? Additionally, this is reversing the index, then using that intermediate result to reindex, so this is essentially a TWO step operation (when it could've been just one).

  • df.sort_index(ascending=False) may work in most cases where you have a simple range index, but this assumes your index was sorted in ascending order and so doesn't generalize well.

  • PLEASE do not use iterrows. I see some options suggesting iterating in reverse. Whatever your use case, there is likely a vectorized method available, but if there isn't then you can use something a little more reasonable such as list comprehensions. See How to iterate over rows in a DataFrame in Pandas for more detail on why iterrows is an antipattern.

ldmtwo
  • 419
  • 5
  • 14
cs95
  • 379,657
  • 97
  • 704
  • 746
  • 1
    One caveat: slicing returns an NDFrame instead of a DataFrame, which makes type checkers unhappy. – knite Aug 26 '21 at 18:12
  • @knite I'm surprised one is not subclassing the other, weird! – cs95 Oct 01 '21 at 21:35
  • Downvoting for **FALSE** claim that it does reverse the index as well (just tested on `pd.read_json(...)`). Ofc this naturally also impacts followup claims in this answer. – MrCC Jun 26 '22 at 12:53
  • @MrCC not sure what read_json is doing for you but running `data[::-1]` on the *OP's code* does reverse the index. As long as it works *in the context of the question*, I strongly disagree with your claim that my answer is false. Please feel free to provide a counter example for which this doesn't work, as saying it didn't work for you isn't enough to convince me this is incorrect. Thanks – cs95 Jul 23 '22 at 10:54
28

None of the existing answers resets the index after reversing the dataframe.

For this, do the following:

 data[::-1].reset_index()

Here's a utility function that also removes the old index column, as per @Tim's comment:

def reset_my_index(df):
  res = df[::-1].reset_index(drop=True)
  return(res)

Simply pass your dataframe into the function

Cybernetic
  • 12,628
  • 16
  • 93
  • 132
  • 4
    You probably want to have `drop=True`, i.e: `data[::-1].reset_index(drop=True)`, otherwise the old index will be added as a column on the DataFrame. – Tim Jun 05 '20 at 20:32
  • Why would you want to do this? – endolith Jul 11 '20 at 23:57
  • 2
    @endolith Some libraries expect the data frame to be indexed. For example, some time series forecasting libraries expect an indexed frame as input so that it can model a time series while remaining agnostic to the time step (day, month, year, etc.). So you may be working with a data frame, do a transformation on it, which messes up the indexing. It’s common to thus reindex the frame. – Cybernetic Jul 12 '20 at 00:23
10

One way to do this if dealing with sorted range index is:

data = data.sort_index(ascending=False)

This approach has the benefits of (1) being a single line, (2) not requiring a utility function, and most importantly (3) not actually changing any of the data in the dataframe.

Caveat: this works by sorting the index in descending order and so may not always be appropriate or generalize for any given Dataframe.

cs95
  • 379,657
  • 97
  • 704
  • 746
Parker Hancock
  • 111
  • 1
  • 2
  • This would work if you are trying to reverse the dataframe (which would be to reverse the index). If you are trying to reverse by certain column, let's say `Odd`, you can do `data.sort_values(by=['Odd'])` – rrlamichhane Nov 19 '20 at 19:56
  • 1
    Only works if the index was already sorted in ascending order to begin with, so this does not generalize well. – cs95 Dec 21 '20 at 04:42
1

This works:

    for i,r in data[::-1].iterrows():
        print(r['Odd'], r['Even'])
  • 1
    This works but is the same as the second answer with the horrible addition of `iterrows`. Not necessary, please! – cs95 Dec 21 '20 at 04:43
0

df.loc[reversed(df.index)]

This is probably more explicit and readable than negative slicing.