web-dev-qa-db-ja.com

辞書のリストで次の変換を行うPythonの方法は何ですか?

このような辞書のリストがあります:

l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]

そして、私はこのフォームの出力を取得したいと思います:

>>> [('foo', 'bar'), ([1,2,3,4], [5,6,7,8])]

しかし、for- loopingとappendingを除いて、解決策が見当たりません。これを行うよりも賢い方法はありますか?

names = []
values = []
for d in l:
    names.append(d['name'])
    values.append(d['values'])
30
oarfish

ジェネレーター式を使用:

l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
v = [Tuple(k["name"] for k in l), Tuple(k["values"] for k in l)]
print(v)

出力:

[('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]
33
eyllanesc

このコードを一般消費者向けに書いている場合、リストの内包表記(eyllanescのように)を使用します。しかし、ただの楽しみのために、これはforsを使用しないワンライナーです。

>>> l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
>>> list(Zip(*map(dict.values, l)))
[('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]

(これは辞書が挿入順序を保持する場合にのみ確実に機能することに注意してください。これはPythonのすべてのバージョンに当てはまりません。CPython3.6は実装の詳細としてそれを行いますが、3.7

プロセスの内訳:

  • dict.valuesはdict_valuesオブジェクトを返します。これは、dictのすべての値を含む反復可能なオブジェクトです。
  • maplの各辞書を取り、その辞書に対してdict.valuesを呼び出し、dict_valuesオブジェクトの反復可能オブジェクトを返します。
  • Zip(*thing)は、古典的な「転置」レシピで、イテラブルの反復可能なものを取り、それを効果的に斜めに反転させます。例えば。 [[a、b]、[c、d]]は[[a、c]、[b、d]]になります。これにより、すべての名前が1つのTupleに入れられ、すべての値が別のTupleに入れられます。
  • listは、Zipオブジェクトをリストに変換します。
24
Kevin

operator.itemgetter toguaranteeの値の順序を使用できます。

from operator import itemgetter

fields = ('name', 'values')
res = list(Zip(*map(itemgetter(*fields), L)))

print(res)

[('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]

Python 3.6+を想定して、入力リスト内の辞書の適切な挿入順序を保証できない場合、上記のように明示的に順序を定義する必要があります。

パフォーマンス

"Tuple comprehensions"のリストは機能しますが、2つ以上のフィールドを照会すると読み取り不能andになります:

from operator import itemgetter

n = 10**6
L = [{'name': 'foo', 'values': [1,2,3,4], 'name2': 'Zoo', 'name3': 'xyz',
      'name4': 'def'}, {'name': 'bar', 'values': [5,6,7,8], 'name2': 'bart',
      'name3': 'abc', 'name4': 'ghi'}] * n

%timeit [Tuple(k["name"] for k in L), Tuple(k["values"] for k in L),\
         Tuple(k["name2"] for k in L), Tuple(k["name3"] for k in L),
         Tuple(k["name4"] for k in L)]

%timeit fields = ('name', 'values', 'name2', 'name3' ,'name4');\
        list(Zip(*map(itemgetter(*fields), L)))

1 loop, best of 3: 1.25 s per loop
1 loop, best of 3: 1.04 s per loop
10
jpp

これはあなたが念頭に置いていたものではないかもしれませんが、このような表形式のデータの場合、長期的にはpandasが通常最良のソリューションであることがわかります。

>>> import pandas as pd
>>> l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
>>> df = pd.DataFrame(l)
  name        values
0  foo  [1, 2, 3, 4]
1  bar  [5, 6, 7, 8]

通常、必要なことにはデータフレームを直接使用しますが、リストベースのデータ構造に変換することもできます。

>>> df['name'].tolist(), df['values'].tolist()
(['foo', 'bar'], [[1, 2, 3, 4], [5, 6, 7, 8]]) 
5
Kale Kundert

パフォーマンスについてはわかりませんが、Zip()を使用して展開する別の方法があります。

list(Zip(*[Tuple(i.values()) for i in l]))

# [('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]

編集:@DeepSpaceが指摘したように、さらに次のように減らすことができます。

list(Zip(*(i.values() for i in l)))

順序を自分で定義したい場合の、より長い、しかしより明確な答えは次のとおりです。

list(Zip(*(Tuple(map(lambda k: i.get(k), ('name', 'values'))) for i in l)))

# [('foo', 'bar'), ([1, 2, 3, 4], [5, 6, 7, 8])]
4
Roca

これにマップを使用

names = Tuple(map(lambda d: d['name'], l))
values = Tuple(map(lambda d: d['values'], l))
result = [names, values]
3
user3142459

再帰的な方法は次のとおりです。

def trans(l):
  if l:
    res = trans(l[1:])
    res[0], res[1] = (l[0]['name'],) + res[0], (l[0]['values'],) + res[1]
    return res
  return [(),()]
0
greenBox

まず、あなたのコードは素晴らしく、読みやすく、効率的です。ただし、おそらくタプルのリストは必要ないことに注意してください。 タプルは不変 なので、namesに別の名前を追加することはできません。

単一の辞書で

namesが一意の場合、dictのリストを大きなdictに変換できます。

>>> l = [{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}]
>>> data = {d['name']:d['values'] for d in l}
>>> data
{'foo': [1, 2, 3, 4], 'bar': [5, 6, 7, 8]}

必要な情報を直接取得できます。

>>> data.keys()
dict_keys(['foo', 'bar'])
>>> data.values()
dict_values([[1, 2, 3, 4], [5, 6, 7, 8]])

リストのリストが本当に必要な場合:

>>> [list(data.keys()), list(data.values())]
[['foo', 'bar'], [[1, 2, 3, 4], [5, 6, 7, 8]]]

パンダと

辞書の大きなリストを使用している場合は、 pandas を検討することをお勧めします。

DataFrame を直接初期化できます:

>>> import pandas as pd
>>> df = pd.DataFrame([{'name': 'foo', 'values': [1,2,3,4]}, {'name': 'bar', 'values': [5,6,7,8]}])
>>> df
  name        values
0  foo  [1, 2, 3, 4]
1  bar  [5, 6, 7, 8]

イテレート可能な名前が必要な場合は、対応する列を取得できます。

>>> df['name']
0    foo
1    bar
Name: name, dtype: object

名前のリストが本当に必要な場合:

>>> list(df['name'])
['foo', 'bar']

名前と値を一緒に取得するには:

>>> df.values.T
array([['foo', 'bar'],
       [list([1, 2, 3, 4]), list([5, 6, 7, 8])]], dtype=object)
0
Eric Duminil