Объедините три столбца в один в CSV-файле с питоном и пандами
Привет я пытаюсь объединить несколько существующих столбцов в 1 новый столбец, а затем удалить три исходных в CSV-файл. Я пытался сделать это с пандами, но мне не повезло. Я довольно новичок в питоне.
Мой код сначала объединяет несколько файлов CSV в одном каталоге, а затем пытается манипулировать столбцами. Первый комбинат работает, и я получаю файл output.csv с объединенными данными, однако комбинат столбцов - нет.
import glob
import pandas as pd
interesting_files = glob.glob("*.csv")
header_saved = False
with open('output.csv','wb') as fout:
for filename in interesting_files:
with open(filename) as fin:
header = next(fin)
if not header_saved:
fout.write(header)
header_saved = True
for line in fin:
fout.write(line)
df = pd.read_csv("output.csv")
df['HostAffected']=df['Host'] + "/" + df['Protocol'] + "/" + df['Port']
df.to_csv("newoutput.csv")
Эффективно превращая это:
Host,Protocol,Port
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,49707
10.0.0.10,tcp,49672
10.0.0.10,tcp,49670
во что-то вроде этого:
HostsAffected
10.0.0.10/tcp/445
10.0.0.10/tcp/445
10.0.0.10/tcp/445
10.0.0.10/tcp/445
10.0.0.10/tcp/445
10.0.0.10/tcp/445
10.0.0.11/tcp/445
10.0.0.11/tcp/49707
10.0.0.11/tcp/49672
10.0.0.11/tcp/49670
10.0.0.11/tcp/49668
10.0.0.11/tcp/49667
Однако есть и другие столбцы в csv.
Я не кодер, я просто пытаюсь решить проблему, любая помощь высоко ценится.
3 ответа
На мой взгляд, у нас есть три варианта:
%timeit df['Host'] + "/" + df['Protocol'] + "/" + df['Port'].map(str)
%timeit ['/'.join(i) for i in zip(df['Host'],df['Protocol'],df['Port'].map(str))]
%timeit ['/'.join(i) for i in df[['Host','Protocol','Port']].astype(str).values]
Сроки:
10 loops, best of 3: 39.7 ms per loop
10 loops, best of 3: 35.9 ms per loop
10 loops, best of 3: 162 ms per loop
Как бы медленно я не думал, что это будет ваш самый читаемый подход:
import pandas as pd
data = '''\
ID,Host,Protocol,Port
1,10.0.0.10,tcp,445
1,10.0.0.10,tcp,445
1,10.0.0.10,tcp,445
1,10.0.0.10,tcp,445
1,10.0.0.10,tcp,445
1,10.0.0.10,tcp,445
1,10.0.0.10,tcp,445
1,10.0.0.10,tcp,49707
1,10.0.0.10,tcp,49672
1,10.0.0.10,tcp,49670'''
df = pd.read_csv(pd.compat.StringIO(data)) # Recreates a sample dataframe
cols = ['Host','Protocol','Port']
newcol = ['/'.join(i) for i in df[cols].astype(str).values]
df = df.assign(HostAffected=newcol).drop(cols, 1)
print(df)
Возвращает:
ID HostAffected
0 1 10.0.0.10/tcp/445
1 1 10.0.0.10/tcp/445
2 1 10.0.0.10/tcp/445
3 1 10.0.0.10/tcp/445
4 1 10.0.0.10/tcp/445
5 1 10.0.0.10/tcp/445
6 1 10.0.0.10/tcp/445
7 1 10.0.0.10/tcp/49707
8 1 10.0.0.10/tcp/49672
9 1 10.0.0.10/tcp/49670
Это можно сделать несколькими способами: либо использовать векторизованные функции для объединения серий, либо использовать lambda
функция с pd.Series.apply
,
Векторизованное решение
Не забудьте использовать нечисловые типы как str
,
df['HostAffected'] = df['Host'] + '/' + df['Protocol'] + '/' + df['Port'].map(str)
Примечание по производительности: преобразование серии целых чисел в строки - почему применяется намного быстрее, чем astype?
Применять lambda
функция
df['HostsAffected'] = df.apply(lambda x: '/'.join(list(map(str, x))), axis=1)
В обоих решениях вы можете просто фильтровать по этому столбцу, чтобы удалить все остальные:
df = df[['HostsAffected']]
Полный пример
from io import StringIO
import pandas as pd
mystr = StringIO("""Host,Protocol,Port
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,49707
10.0.0.10,tcp,49672
10.0.0.10,tcp,49670""")
# replace mystr with 'file.csv'
df = pd.read_csv(mystr)
# combine columns
df['HostsAffected'] = df['Host'] + '/' + df['Protocol'] + '/' + df['Port'].map(str)
# include only new columns
df = df[['HostsAffected']]
Результат:
print(df)
HostsAffected
0 10.0.0.10/tcp/445
1 10.0.0.10/tcp/445
2 10.0.0.10/tcp/445
3 10.0.0.10/tcp/445
4 10.0.0.10/tcp/445
5 10.0.0.10/tcp/445
6 10.0.0.10/tcp/445
7 10.0.0.10/tcp/49707
8 10.0.0.10/tcp/49672
9 10.0.0.10/tcp/49670
Вот как вы можете это сделать:
dt = """Host,Protocol,Port
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,445
10.0.0.10,tcp,49707
10.0.0.10,tcp,49672
10.0.0.10,tcp,49670"""
tdf = pd.read_csv(pd.compat.StringIO(dt))
tdf['HostsAffected'] = tdf.apply(lambda x: '{}/{}/{}'.format(x['Host'] , x['Protocol'] , x['Port']), axis=1)
tdf = tdf[['HostsAffected']]
tdf.to_csv(<path-to-save-csv-file>)
Это будет вывод:
HostsAffected
0 10.0.0.10/tcp/445
1 10.0.0.10/tcp/445
2 10.0.0.10/tcp/445
3 10.0.0.10/tcp/445
4 10.0.0.10/tcp/445
5 10.0.0.10/tcp/445
6 10.0.0.10/tcp/445
7 10.0.0.10/tcp/49707
8 10.0.0.10/tcp/49672
9 10.0.0.10/tcp/49670
Если вы читаете CSV из файла, отредактируйте строку read_csv следующим образом:
tdf = pd.read_csv(<path-to-the-file>)