How are iloc and loc different?

前端 未结 3 784
孤城傲影
孤城傲影 2020-11-21 06:36

Can someone explain how these two methods of slicing are different?
I\'ve seen the docs, and I\'ve seen these answers, but I still find myself unable to explain how th

相关标签:
3条回答
  • 2020-11-21 06:50

    iloc works based on integer positioning. So no matter what your row labels are, you can always, e.g., get the first row by doing

    df.iloc[0]
    

    or the last five rows by doing

    df.iloc[-5:]
    

    You can also use it on the columns. This retrieves the 3rd column:

    df.iloc[:, 2]    # the : in the first position indicates all rows
    

    You can combine them to get intersections of rows and columns:

    df.iloc[:3, :3] # The upper-left 3 X 3 entries (assuming df has 3+ rows and columns)
    

    On the other hand, .loc use named indices. Let's set up a data frame with strings as row and column labels:

    df = pd.DataFrame(index=['a', 'b', 'c'], columns=['time', 'date', 'name'])
    

    Then we can get the first row by

    df.loc['a']     # equivalent to df.iloc[0]
    

    and the second two rows of the 'date' column by

    df.loc['b':, 'date']   # equivalent to df.iloc[1:, 1]
    

    and so on. Now, it's probably worth pointing out that the default row and column indices for a DataFrame are integers from 0 and in this case iloc and loc would work in the same way. This is why your three examples are equivalent. If you had a non-numeric index such as strings or datetimes, df.loc[:5] would raise an error.

    Also, you can do column retrieval just by using the data frame's __getitem__:

    df['time']    # equivalent to df.loc[:, 'time']
    

    Now suppose you want to mix position and named indexing, that is, indexing using names on rows and positions on columns (to clarify, I mean select from our data frame, rather than creating a data frame with strings in the row index and integers in the column index). This is where .ix comes in:

    df.ix[:2, 'time']    # the first two rows of the 'time' column
    

    I think it's also worth mentioning that you can pass boolean vectors to the loc method as well. For example:

     b = [True, False, True]
     df.loc[b] 
    

    Will return the 1st and 3rd rows of df. This is equivalent to df[b] for selection, but it can also be used for assigning via boolean vectors:

    df.loc[b, 'name'] = 'Mary', 'John'
    
    0 讨论(0)
  • 2020-11-21 06:59

    Note: in pandas version 0.20.0 and above, ix is deprecated and the use of loc and iloc is encouraged instead. I have left the parts of this answer that describe ix intact as a reference for users of earlier versions of pandas. Examples have been added below showing alternatives to ix.


    First, here's a recap of the three methods:

    • loc gets rows (or columns) with particular labels from the index.
    • iloc gets rows (or columns) at particular positions in the index (so it only takes integers).
    • ix usually tries to behave like loc but falls back to behaving like iloc if a label is not present in the index.

    It's important to note some subtleties that can make ix slightly tricky to use:

    • if the index is of integer type, ix will only use label-based indexing and not fall back to position-based indexing. If the label is not in the index, an error is raised.

    • if the index does not contain only integers, then given an integer, ix will immediately use position-based indexing rather than label-based indexing. If however ix is given another type (e.g. a string), it can use label-based indexing.


    To illustrate the differences between the three methods, consider the following Series:

    >>> s = pd.Series(np.nan, index=[49,48,47,46,45, 1, 2, 3, 4, 5])
    >>> s
    49   NaN
    48   NaN
    47   NaN
    46   NaN
    45   NaN
    1    NaN
    2    NaN
    3    NaN
    4    NaN
    5    NaN
    

    We'll look at slicing with the integer value 3.

    In this case, s.iloc[:3] returns us the first 3 rows (since it treats 3 as a position) and s.loc[:3] returns us the first 8 rows (since it treats 3 as a label):

    >>> s.iloc[:3] # slice the first three rows
    49   NaN
    48   NaN
    47   NaN
    
    >>> s.loc[:3] # slice up to and including label 3
    49   NaN
    48   NaN
    47   NaN
    46   NaN
    45   NaN
    1    NaN
    2    NaN
    3    NaN
    
    >>> s.ix[:3] # the integer is in the index so s.ix[:3] works like loc
    49   NaN
    48   NaN
    47   NaN
    46   NaN
    45   NaN
    1    NaN
    2    NaN
    3    NaN
    

    Notice s.ix[:3] returns the same Series as s.loc[:3] since it looks for the label first rather than working on the position (and the index for s is of integer type).

    What if we try with an integer label that isn't in the index (say 6)?

    Here s.iloc[:6] returns the first 6 rows of the Series as expected. However, s.loc[:6] raises a KeyError since 6 is not in the index.

    >>> s.iloc[:6]
    49   NaN
    48   NaN
    47   NaN
    46   NaN
    45   NaN
    1    NaN
    
    >>> s.loc[:6]
    KeyError: 6
    
    >>> s.ix[:6]
    KeyError: 6
    

    As per the subtleties noted above, s.ix[:6] now raises a KeyError because it tries to work like loc but can't find a 6 in the index. Because our index is of integer type ix doesn't fall back to behaving like iloc.

    If, however, our index was of mixed type, given an integer ix would behave like iloc immediately instead of raising a KeyError:

    >>> s2 = pd.Series(np.nan, index=['a','b','c','d','e', 1, 2, 3, 4, 5])
    >>> s2.index.is_mixed() # index is mix of different types
    True
    >>> s2.ix[:6] # now behaves like iloc given integer
    a   NaN
    b   NaN
    c   NaN
    d   NaN
    e   NaN
    1   NaN
    

    Keep in mind that ix can still accept non-integers and behave like loc:

    >>> s2.ix[:'c'] # behaves like loc given non-integer
    a   NaN
    b   NaN
    c   NaN
    

    As general advice, if you're only indexing using labels, or only indexing using integer positions, stick with loc or iloc to avoid unexpected results - try not use ix.


    Combining position-based and label-based indexing

    Sometimes given a DataFrame, you will want to mix label and positional indexing methods for the rows and columns.

    For example, consider the following DataFrame. How best to slice the rows up to and including 'c' and take the first four columns?

    >>> df = pd.DataFrame(np.nan, 
                          index=list('abcde'),
                          columns=['x','y','z', 8, 9])
    >>> df
        x   y   z   8   9
    a NaN NaN NaN NaN NaN
    b NaN NaN NaN NaN NaN
    c NaN NaN NaN NaN NaN
    d NaN NaN NaN NaN NaN
    e NaN NaN NaN NaN NaN
    

    In earlier versions of pandas (before 0.20.0) ix lets you do this quite neatly - we can slice the rows by label and the columns by position (note that for the columns, ix will default to position-based slicing since 4 is not a column name):

    >>> df.ix[:'c', :4]
        x   y   z   8
    a NaN NaN NaN NaN
    b NaN NaN NaN NaN
    c NaN NaN NaN NaN
    

    In later versions of pandas, we can achieve this result using iloc and the help of another method:

    >>> df.iloc[:df.index.get_loc('c') + 1, :4]
        x   y   z   8
    a NaN NaN NaN NaN
    b NaN NaN NaN NaN
    c NaN NaN NaN NaN
    

    get_loc() is an index method meaning "get the position of the label in this index". Note that since slicing with iloc is exclusive of its endpoint, we must add 1 to this value if we want row 'c' as well.

    There are further examples in pandas' documentation here.

    0 讨论(0)
  • 2020-11-21 07:14

    In my opinion, the accepted answer is confusing, since it uses a DataFrame with only missing values. I also do not like the term position-based for .iloc and instead, prefer integer location as it is much more descriptive and exactly what .iloc stands for. The key word is INTEGER - .iloc needs INTEGERS.

    See my extremely detailed blog series on subset selection for more


    .ix is deprecated and ambiguous and should never be used

    Because .ix is deprecated we will only focus on the differences between .loc and .iloc.

    Before we talk about the differences, it is important to understand that DataFrames have labels that help identify each column and each index. Let's take a look at a sample DataFrame:

    df = pd.DataFrame({'age':[30, 2, 12, 4, 32, 33, 69],
                       'color':['blue', 'green', 'red', 'white', 'gray', 'black', 'red'],
                       'food':['Steak', 'Lamb', 'Mango', 'Apple', 'Cheese', 'Melon', 'Beans'],
                       'height':[165, 70, 120, 80, 180, 172, 150],
                       'score':[4.6, 8.3, 9.0, 3.3, 1.8, 9.5, 2.2],
                       'state':['NY', 'TX', 'FL', 'AL', 'AK', 'TX', 'TX']
                       },
                      index=['Jane', 'Nick', 'Aaron', 'Penelope', 'Dean', 'Christina', 'Cornelia'])
    

    All the words in bold are the labels. The labels, age, color, food, height, score and state are used for the columns. The other labels, Jane, Nick, Aaron, Penelope, Dean, Christina, Cornelia are used for the index.


    The primary ways to select particular rows in a DataFrame are with the .loc and .iloc indexers. Each of these indexers can also be used to simultaneously select columns but it is easier to just focus on rows for now. Also, each of the indexers use a set of brackets that immediately follow their name to make their selections.

    .loc selects data only by labels

    We will first talk about the .loc indexer which only selects data by the index or column labels. In our sample DataFrame, we have provided meaningful names as values for the index. Many DataFrames will not have any meaningful names and will instead, default to just the integers from 0 to n-1, where n is the length of the DataFrame.

    There are three different inputs you can use for .loc

    • A string
    • A list of strings
    • Slice notation using strings as the start and stop values

    Selecting a single row with .loc with a string

    To select a single row of data, place the index label inside of the brackets following .loc.

    df.loc['Penelope']
    

    This returns the row of data as a Series

    age           4
    color     white
    food      Apple
    height       80
    score       3.3
    state        AL
    Name: Penelope, dtype: object
    

    Selecting multiple rows with .loc with a list of strings

    df.loc[['Cornelia', 'Jane', 'Dean']]
    

    This returns a DataFrame with the rows in the order specified in the list:

    Selecting multiple rows with .loc with slice notation

    Slice notation is defined by a start, stop and step values. When slicing by label, pandas includes the stop value in the return. The following slices from Aaron to Dean, inclusive. Its step size is not explicitly defined but defaulted to 1.

    df.loc['Aaron':'Dean']
    

    Complex slices can be taken in the same manner as Python lists.

    .iloc selects data only by integer location

    Let's now turn to .iloc. Every row and column of data in a DataFrame has an integer location that defines it. This is in addition to the label that is visually displayed in the output. The integer location is simply the number of rows/columns from the top/left beginning at 0.

    There are three different inputs you can use for .iloc

    • An integer
    • A list of integers
    • Slice notation using integers as the start and stop values

    Selecting a single row with .iloc with an integer

    df.iloc[4]
    

    This returns the 5th row (integer location 4) as a Series

    age           32
    color       gray
    food      Cheese
    height       180
    score        1.8
    state         AK
    Name: Dean, dtype: object
    

    Selecting multiple rows with .iloc with a list of integers

    df.iloc[[2, -2]]
    

    This returns a DataFrame of the third and second to last rows:

    Selecting multiple rows with .iloc with slice notation

    df.iloc[:5:3]
    


    Simultaneous selection of rows and columns with .loc and .iloc

    One excellent ability of both .loc/.iloc is their ability to select both rows and columns simultaneously. In the examples above, all the columns were returned from each selection. We can choose columns with the same types of inputs as we do for rows. We simply need to separate the row and column selection with a comma.

    For example, we can select rows Jane, and Dean with just the columns height, score and state like this:

    df.loc[['Jane', 'Dean'], 'height':]
    

    This uses a list of labels for the rows and slice notation for the columns

    We can naturally do similar operations with .iloc using only integers.

    df.iloc[[1,4], 2]
    Nick      Lamb
    Dean    Cheese
    Name: food, dtype: object
    

    Simultaneous selection with labels and integer location

    .ix was used to make selections simultaneously with labels and integer location which was useful but confusing and ambiguous at times and thankfully it has been deprecated. In the event that you need to make a selection with a mix of labels and integer locations, you will have to make both your selections labels or integer locations.

    For instance, if we want to select rows Nick and Cornelia along with columns 2 and 4, we could use .loc by converting the integers to labels with the following:

    col_names = df.columns[[2, 4]]
    df.loc[['Nick', 'Cornelia'], col_names] 
    

    Or alternatively, convert the index labels to integers with the get_loc index method.

    labels = ['Nick', 'Cornelia']
    index_ints = [df.index.get_loc(label) for label in labels]
    df.iloc[index_ints, [2, 4]]
    

    Boolean Selection

    The .loc indexer can also do boolean selection. For instance, if we are interested in finding all the rows wher age is above 30 and return just the food and score columns we can do the following:

    df.loc[df['age'] > 30, ['food', 'score']] 
    

    You can replicate this with .iloc but you cannot pass it a boolean series. You must convert the boolean Series into a numpy array like this:

    df.iloc[(df['age'] > 30).values, [2, 4]] 
    

    Selecting all rows

    It is possible to use .loc/.iloc for just column selection. You can select all the rows by using a colon like this:

    df.loc[:, 'color':'score':2]
    


    The indexing operator, [], can select rows and columns too but not simultaneously.

    Most people are familiar with the primary purpose of the DataFrame indexing operator, which is to select columns. A string selects a single column as a Series and a list of strings selects multiple columns as a DataFrame.

    df['food']
    
    Jane          Steak
    Nick           Lamb
    Aaron         Mango
    Penelope      Apple
    Dean         Cheese
    Christina     Melon
    Cornelia      Beans
    Name: food, dtype: object
    

    Using a list selects multiple columns

    df[['food', 'score']]
    

    What people are less familiar with, is that, when slice notation is used, then selection happens by row labels or by integer location. This is very confusing and something that I almost never use but it does work.

    df['Penelope':'Christina'] # slice rows by label
    

    df[2:6:2] # slice rows by integer location
    

    The explicitness of .loc/.iloc for selecting rows is highly preferred. The indexing operator alone is unable to select rows and columns simultaneously.

    df[3:5, 'color']
    TypeError: unhashable type: 'slice'
    
    0 讨论(0)
提交回复
热议问题