У меня есть вопрос о Python и дизайне базы данных. Допустим, у меня есть большая база данных (табличная, как SQL) с множеством столбцов (функций) и миллионами строк (записей).
Я хотел бы извлечь каждую запись со многими функциями для совместной обработки.
База данных очень большая, например, содержит миллионы записей и десятки тысяч функций. Что я сейчас делаю, так это извлекаю каждый отдельный столбец в список, чтобы на каждую запись можно было ссылаться с одним и тем же индексом в разных списках. Есть лучший способ сделать это? Я думаю, будет ли фрейм данных лучше?
В качестве примера я обрабатываю 3 записи (на самом деле у меня миллионы записей), чтобы я мог отобразить их, как в операторе печати:
namelist = ['Peter', 'John', 'Susan']
agelist = [16, 17, 18]
activitylist = ['play tennis', 'play chess', 'swim']
for i, name in enumerate(namelist):
print('Hi, my name is ' + name + '. I am ' + str(agelist[i]) + ' years old and I like to ' + activitylist[i])
Выход:
Hi, my name is Peter. I am 16 years old and I like to play tennis
Hi, my name is John. I am 17 years old and I like to play chess
Hi, my name is Susan. I am 18 years old and I like to swim
могу ли я узнать, как сделать векторизацию фрейма данных для примера конкатенации строк?
Ответ @perpetualstudent показывает, как векторизовать
Я бы использовал приведенные ниже коды настройки для уточнения своего ответа и для измерения времени. Обратите внимание, что в фрейме данных содержится 3 миллиона записей, и в качестве примера я выбрал операцию добавления этих данных к переменной.
import pandas as pd
import time
namelist = ['Peter', 'John', 'Susan'] *1000000
agelist = [16, 17, 18] *1000000
activitylist = ['play tennis', 'play chess', 'swim'] *1000000
df = pd.DataFrame({'name': namelist, 'age': agelist, 'activity': activitylist})
Ваш исходный метод уже очень эффективен, особенно если данные легко поступают в отдельные списки, а операция на моей машине занимает около 1,8 с:
start = time.time()
result = []
for i, name in enumerate(namelist):
result.append('Hi, my name is ' + name + '. I am ' + str(agelist[i]) + ' years old and I like to ' + activitylist[i])
end = time.time()
print(end - start)
Выход:
1.7815442085266113
Позвольте мне разработать несколько альтернативных методов, если данные поступают в такой фрейм данных:
df = pd.DataFrame({'name': namelist, 'age': agelist, 'activity': activitylist})
Метод (1) с использованием df.iterrows()
Этот метод перебирает строку за строкой, и это очень медленно. В документации по итерации есть окно с предупреждением:
Итерация по объектам pandas обычно медленная. Во многих случаях повторение строк вручную не требуется...
В любом случае этот метод занимает около 112,3 с на моей машине:
start = time.time()
result = []
for i, row in df.iterrows():
result.append('Hi, my name is ' + row['name'] + '. I am ' + str(row['age']) + ' years old and I like to ' + row['activity'])
end = time.time()
print(end - start)
Выход:
112.2983672618866
Метод (2) с использованием df.to_numpy()
Этот метод преобразует фрейм данных в массив numpy по строкам, а затем выполняет итерацию по каждому массиву, используя index. Это самое близкое к манипулированию списком, которое у вас было изначально. На моей машине это занимает около 2,7 с:
start = time.time()
result = []
for row in df.to_numpy():
result.append('Hi, my name is ' + row[0] + '. I am ' + str(row[1]) + ' years old and I like to ' + row[2])
end = time.time()
print(end - start)
Выход:
2.7370002269744873
Метод (3) Векторизация
Невекторизованный метод (например, df.iterrows()
или df.apply()
) вызывает функцию Python для каждой строки, и эта функция Python выполняет дополнительные операции. Напротив, эта векторизованная операция намного быстрее, потому что она позволяет избежать использования кода Python во внутренних циклах. На моей машине это занимает около 1,9 с:
start = time.time()
df.age = df.age.astype('str')
df['result'] = 'Hi, my name is ' + df.name + '. I am ' + df.age + ' years old and I like to ' + df.activity
result = df.result.tolist()
end = time.time()
print(end - start)
Выход:
1.8785054683685303
Метод (4) Понимание списка с помощью Zip
Этот метод, предложенный @Stuart, кажется самым быстрым! На моей машине это заняло всего около 0,7 с:
start = time.time()
result = [f'Hi, my name is {name}. I am {age} years old and I like to {activity}'
for name, age, activity in zip(namelist, agelist, activitylist)]
end = time.time()
print(end - start)
Выход:
0.7034788322448731
Спасибо, я бы использовал метод (3) векторизации, поскольку исходные данные поступают в табличном формате!
Кажется, самый быстрый способ сделать это — объединить списки вместе, а затем использовать понимание списка с форматированной строкой:
result = [f'Hi, my name is {name}. I am {age} years old and I like to {activity}'
for name, age, activity in zip(namelist, agelist, activitylist)]
Метод векторизованных панд (как показано в ответе @perpetualstudent) кажется немного медленнее, чем этот, даже если вы можете хранить age
в виде строк, чтобы преобразование строк не требовалось. Очевидно, это связано с тем, что строковые операции нельзя ускорить с помощью векторизации в той же степени, что и математические операции в пандах.
Кроме того, в вашем примере большую часть времени будет занимать
print
вывод миллионов строк в консоль, поэтому способ хранения данных менее актуален. Если бы вы могли записать вывод в файл или просто сохранить его в другой переменной, это было бы намного быстрее.