Самый быстрый способ перебрать Pandas DataFrame и вставить строку - PullRequest
0 голосов
/ 06 ноября 2018

Я создаю инструмент, который поможет автоматизировать анализ данных из нескольких лабораторных установок на еженедельной основе. Текстовый файл с разделителями табуляции создается каждый день. Каждая строка представляет данные, взятые каждые 2 секунды, поэтому имеется 43200 строк и много столбцов (каждый файл равен 75 МБ)

Я загружаю семь текстовых файлов, используя pandas.readcsv, и извлекаю только три нужных мне столбца в кадр данных pandas. Это медленнее, чем хотелось бы, но приемлемо. Затем я отображаю данные в автономном режиме Plotly для просмотра интерактивного графика. Это запланированное задание, запускаемое раз в неделю.

Данные отображаются в зависимости от даты и времени. Часто тестовые настройки временно недоступны, и в данных будут пропуски. К сожалению, при построении графика все данные соединяются линиями, даже если тест был в автономном режиме в течение нескольких часов или дней.

Единственный способ предотвратить это - вставить строку с датой между двумя датами с фактическими данными и NaN для всех отсутствующих данных. Я реализовал это для отсутствующего файла данных достаточно легко, однако я хочу обобщить это для любых пробелов в данных, превышающих определенный период времени. Я нашел решение, которое, кажется, работает, но ДЕЙСТВИТЕЛЬНО медленно:

# alldata is a pandas dataframe with 302,000 rows and 4 columns
# one datetime column and three float32 columns

alldata_gaps  = pandas.DataFrame() #new dataframe with gaps in it

#iterate over all rows. If the datetime difference between 
#two consecutive rows is more than one minute, insert a gap row.

for i in range(0, len(alldata)):
    alldata_gaps = alldata_gaps.append(alldata.iloc[i])
    if alldata.iloc[i+1, 0]-alldata.iloc[i,0] > datetime.timedelta(minutes=1):
        Series = pandas.Series({'datetime' : alldata.iloc[i,0]
        +datetime.timedelta(seconds=3)})
        alldata_gaps = alldata_gaps.append(Series)
        print(Series)

У кого-нибудь есть предложения, как я мог бы ускорить эту операцию, чтобы она не заняла так много времени?

Вот дропбокс-ссылка на пример файла данных, содержащий всего 100 строк

Вот ссылка на мой текущий скрипт без добавления пробелов

Ответы [ 2 ]

0 голосов
/ 07 ноября 2018

Моя общая идея такая же, как и в ответе jpp: вместо итерации кадра данных (который медленен для объема данных, который у вас есть), вы должны просто определить интересующие строки и работать с ними. Основные отличия: 1) поворот нескольких столбцов в NA и 2) настройка метки времени строки NA, чтобы она была наполовину между окружающими временами

Я добавил объяснения в виде комментариев ...

# after you read in your data, make sure the time column is actually a datetime
df['datetime'] = pd.to_datetime(df['datetime'])

# calculate the (time) difference between a row and the previous row
df['time_diff'] = df['datetime'].diff()

# create a subset of your df where the time difference is greater than
# some threshold. This will be a dataframe of your empty/NA rows.
# I've set a 2 second threshold here because of the sample data you provided, 
# but could be any number of seconds
empty = df[df['time_diff'].dt.total_seconds() > 2].copy()

# calculate the correct timestamp for the NA rows (halfway and evenly spaced)
empty['datetime'] = empty['datetime'] - (empty['time_diff'].shift(-1) / 2)

# set all the columns to NA apart from the datetime column
empty.loc[:, ~empty.columns.isin(['datetime'])] = np.nan

# append this NA/empty dataframe to your original data, and sort by time
df = df.append(empty, ignore_index=True)
df = df.sort_values('datetime').reset_index(drop=True)

# optionally, remove the time_diff column we created at the beginning
df.drop('time_diff', inplace=True, axis=1)

Это даст вам что-то вроде этого:

enter image description here

0 голосов
/ 07 ноября 2018

Почти наверняка ваше узкое место от pd.DataFrame.append:

alldata_gaps = alldata_gaps.append(alldata.iloc[i])
alldata_gaps = alldata_gaps.append(Series)

Кроме того, вы смущенно назвали переменную так же, как объект Pandas pd.Series. Это хорошая практика, чтобы избежать такой двусмысленности.

A намного Более эффективное решение:

  1. Определите время, после которого возникают пробелы.
  2. Создать отдельный фрейм данных с данными за это время + 3 секунды.
  3. Добавить к существующему фрейму данных и отсортировать по времени.

Итак, давайте сделаем удар с примером кадра данных:

# example dataframe setup
df = pd.DataFrame({'Date': ['00:10:15', '00:15:20', '00:15:40', '00:16:50', '00:17:55',
                            '00:19:00', '00:19:10', '00:19:15', '00:19:55', '00:20:58'],
                   'Value': list(range(10))})

df['Date'] = pd.to_datetime('2018-11-06-' + df['Date'])

# find gaps greater than 1 minute
bools = (df['Date'].diff().dt.seconds > 60).shift(-1).fillna(False)
idx = bools[bools].index
# Int64Index([0, 2, 3, 4, 8], dtype='int64')

# construct dataframe to append
df_extra = df.loc[idx].copy().assign(Value=np.nan)

# add 3 seconds
df_extra['Date'] = df_extra['Date'] + pd.to_timedelta('3 seconds')

# append to original
res = df.append(df_extra).sort_values('Date')

Результат:

print(res)

                 Date  Value
0 2018-11-06 00:10:15    0.0
0 2018-11-06 00:10:18    NaN
1 2018-11-06 00:15:20    1.0
2 2018-11-06 00:15:40    2.0
2 2018-11-06 00:15:43    NaN
3 2018-11-06 00:16:50    3.0
3 2018-11-06 00:16:53    NaN
4 2018-11-06 00:17:55    4.0
4 2018-11-06 00:17:58    NaN
5 2018-11-06 00:19:00    5.0
6 2018-11-06 00:19:10    6.0
7 2018-11-06 00:19:15    7.0
8 2018-11-06 00:19:55    8.0
8 2018-11-06 00:19:58    NaN
9 2018-11-06 00:20:58    9.0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...