title: Combine Date Ranges in Pandas Dataframe
body: <p>I have a set of records in Python with an id, at least one attribute, and a set of date ranges.  I would like code that takes each id, and combines all the records where the attributes match and there is no gap in the date range.  </p>
  
  <p>By no gap in date range, I mean that the end date of one record is greater than or equal to the next record for that id.</p>
  
  <p>For instance, a record with id "10", start date "2016-01-01" and end date "2017-01-01" could be merged with another record with that id, a start date of "2017-01-01", and an end date of "2018-01-01", but it could NOT be merged with a record that started on "2017-01-10", because there'd be a gap from 2017-01-01 to 2017-01-09.</p>
  
  <p>Here are some examples --</p>
  
  <p>Have:</p>
  
  <pre><code>FruitID,FruitType,StartDate,EndDate
  1,Apple,2015-01-01,2016-01-01
  1,Apple,2016-01-01,2017-01-01
  1,Apple,2017-01-01,2018-01-01
  2,Orange,2015-01-01,2016-01-01
  2,Orange,2016-05-31,2017-01-01
  2,Orange,2017-01-01,2018-01-01
  3,Banana,2015-01-01,2016-01-01
  3,Banana,2016-01-01,2017-01-01
  3,Blueberry,2017-01-01,2018-01-01
  4,Mango,2015-01-01,2016-01-01
  4,Kiwi,2016-09-15,2017-01-01
  4,Mango,2017-01-01,2018-01-01
  </code></pre>
  
  <p>Want:</p>
  
  <pre><code>FruitID,FruitType,NewStartDate,NewEndDate
  1,Apple,2015-01-01,2018-01-01
  2,Orange,2015-01-01,2016-01-01
  2,Orange,2016-05-31,2018-01-01
  3,Banana,2015-01-01,2017-01-01
  3,Blueberry,2017-01-01,2018-01-01
  4,Mango,2015-01-01,2016-01-01
  4,Kiwi,2016-09-15,2017-01-01
  4,Mango,2017-01-01,2018-01-01
  </code></pre>
  
  <p>My current solution is below.  It provides the results I'm looking for, but performance doesn't seem great for large datasets.  Also, my impression is that you generally want to avoid iterating over individual rows of a dataframe when possible.  Thank you very much for any assistance you can provide!</p>
  
  <pre><code>import pandas as pd
  from dateutil.parser import parse
  
  have = pd.DataFrame.from_items([('FruitID', [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]),
                                  ('FruitType', ['Apple', 'Apple', 'Apple', 'Orange', 'Orange', 'Orange', 'Banana', 'Banana', 'Blueberry', 'Mango', 'Kiwi', 'Mango']),
                                  ('StartDate', [parse(x) for x in ['2015-01-01', '2016-01-01', '2017-01-01', '2015-01-01', '2016-05-31',
                                                                    '2017-01-01', '2015-01-01', '2016-01-01', '2017-01-01', '2015-01-01', '2016-09-15', '2017-01-01']]),
                                  ('EndDate', [parse(x) for x in ['2016-01-01', '2017-01-01', '2018-01-01', '2016-01-01', '2017-01-01',
                                                                  '2018-01-01', '2016-01-01', '2017-01-01', '2018-01-01', '2016-01-01', '2017-01-01', '2018-01-01']])
                                  ])
  
  have.sort_values(['FruitID', 'StartDate'])
  
  rowlist = []
  fruit_cur_row = None
  
  for row in have.itertuples():
      if fruit_cur_row is None:
          fruit_cur_row = row._asdict()
          fruit_cur_row.update(NewStartDate=row.StartDate, NewEndDate=row.EndDate)
  
      elif not(fruit_cur_row.get('FruitType') == row.FruitType):
          rowlist.append(fruit_cur_row)
  
          fruit_cur_row = row._asdict()
          fruit_cur_row.update(NewStartDate=row.StartDate, NewEndDate=row.EndDate)
  
      elif (row.StartDate &lt;= fruit_cur_row.get('NewEndDate')):
          fruit_cur_row['NewEndDate'] = max(fruit_cur_row['NewEndDate'], row.EndDate)
      else:
          rowlist.append(fruit_cur_row)
          fruit_cur_row = row._asdict()
          fruit_cur_row.update(NewStartDate=row.StartDate, NewEndDate=row.EndDate)
  
  rowlist.append(fruit_cur_row)
  have_mrg = pd.DataFrame.from_dict(rowlist)
  print(have_mrg[['FruitID', 'FruitType', 'NewStartDate', 'NewEndDate']])
  </code></pre>