Як зробити гарні приклади відтворюваних панд


221

Провівши пристойну кількість часу, спостерігаючи за обома і Теги на SO, я створюю враження, що pandasпитання рідше містять відтворювані дані. Це те , що R співтовариство було дуже добре про заохочення, і завдяки керівництву , як це , новачки можуть отримати деяку допомогу в складанні цих прикладів. Люди, які вміють читати ці посібники та повертаються із відтворюваними даними, часто матимуть більше шансів отримати відповіді на свої запитання.

Як ми можемо створити хороші відтворювані приклади для pandasзапитань? Прості рамки даних можуть бути складені разом, наприклад:

import pandas as pd
df = pd.DataFrame({'user': ['Bob', 'Jane', 'Alice'], 
                   'income': [40000, 50000, 42000]})

Але багато прикладів наборів даних потребують складнішої структури, наприклад:

  • datetime індекси або дані
  • Кілька категоричних змінних (чи є еквівалент expand.grid()функції R , яка виробляє всі можливі комбінації деяких заданих змінних?)
  • Дані MultiIndex або Panel

Для наборів даних, які важко змити за допомогою декількох рядків коду, чи існує еквівалент R, dput()який дозволяє генерувати код, який можна вставити, щоб відновити структуру даних?


8
Якщо ви копіюєте вихід друку, більшість часу користувачі можуть використовувати read_clipboard () ... за винятком MultiIndex: s. Сказавши це, диктант є гарним доповненням
Енді Хейден

8
На додаток до того, що Енді сказав, я вважаю, що копіювання-вставка df.head(N).to_dict(), де Nє якась розумна кількість, це хороший шлях. Бонус + 1 за додавання прекрасних перерв до виводу. Для часових позначок зазвичай потрібно просто додати from pandas import Timestampїх до вершини коду.
Пол H

Відповіді:


323

Примітка. Ідеї тут досить загальні для Stack Overflow, справді, питань .

Відмова: Написати гарне запитання - ТРУДНО.

Добре:

  • включити невеликий * приклад DataFrame, або як код, який можна виконати:

    In [1]: df = pd.DataFrame([[1, 2], [1, 3], [4, 6]], columns=['A', 'B'])

    або зробити його "копіювати та вставляти" за допомогою pd.read_clipboard(sep='\s\s+'), ви можете відформатувати текст для підсвічування стека переповнення та використовувати Ctrl+ K(або додати чотири пробіли до кожного рядка) або розмістити три тилди над і під кодом з кодом без назви:

    In [2]: df
    Out[2]: 
       A  B
    0  1  2
    1  1  3
    2  4  6

    перевірити pd.read_clipboard(sep='\s\s+')себе.

    * Я дійсно маю на увазі невелике , переважна більшість прикладів DataFrames може бути меншою, ніж потрібно 6 цитатами цитування , і я думаю, що я можу це зробити в 5 рядків. Чи можете ви відтворити помилку df = df.head(), якщо не поспішайте, щоб побачити, чи можете ви скласти невеликий DataFrame, який демонструє проблему, з якою ви стикаєтеся.

    * Кожне правило має виняток, очевидно , один для проблем з продуктивністю ( в цьому випадку , безумовно , використовувати% timeit і , можливо , % prun ), де ви повинні генерувати (розглянути можливість використання np.random.seed тому у нас є такий самий кадр): df = pd.DataFrame(np.random.randn(100000000, 10)). Говорячи, що "зробіть цей код швидким для мене" не є строго темою для сайту ...

  • випишіть бажаний результат (аналогічно вище)

    In [3]: iwantthis
    Out[3]: 
       A  B
    0  1  5
    1  4  6

    Поясніть, з чого походять числа: 5 - це сума стовпця B для рядків, де A дорівнює 1.

  • покажіть код, який ви спробували:

    In [4]: df.groupby('A').sum()
    Out[4]: 
       B
    A   
    1  5
    4  6

    Але скажіть, що неправильно: стовпець A знаходиться в індексі, а не стовпчику.

  • покажіть, що ви провели деякі дослідження ( шукайте документи , шукайте StackOverflow ), дайте резюме:

    У доктрині для суми просто зазначено "Обчислити суму групових значень"

    Документи groupby не дають жодних прикладів для цього.

    Убік: тут потрібно відповісти df.groupby('A', as_index=False).sum().

  • якщо це доречно, що у вас є стовпчики мітки, наприклад, ви переставляєте або щось подібне, то будьте явні та застосуйте pd.to_datetimeїх для гарної міри **.

    df['date'] = pd.to_datetime(df['date']) # this column ought to be date..

    ** Іноді це саме питання: вони були рядками.

Погане:

  • не включайте MultiIndex, який ми не можемо скопіювати та вставити (див. вище), це своєрідна скарга з дисплеєм за замовчуванням панди, але тим не менш дратує:

    In [11]: df
    Out[11]:
         C
    A B   
    1 2  3
      2  6

    Правильний спосіб - включити звичайний DataFrame з set_indexвикликом:

    In [12]: df = pd.DataFrame([[1, 2, 3], [1, 2, 6]], columns=['A', 'B', 'C']).set_index(['A', 'B'])
    
    In [13]: df
    Out[13]: 
         C
    A B   
    1 2  3
      2  6
  • дайте зрозуміти, що це таке, коли даєте бажаний результат:

       B
    A   
    1  1
    5  0

    Будьте конкретні щодо того, як ви отримали цифри (які вони) ... перевірте, чи правильно вони.

  • Якщо ваш код видає помилку, включайте весь слід стека (це можна відредагувати пізніше, якщо це занадто шумно). Покажіть номер рядка (і відповідний рядок вашого коду, проти якого він збільшується).

Потворний:

  • не посилайтеся на csv, до якого ми не маємо доступу (в ідеалі взагалі не посилайтеся на зовнішнє джерело ...)

    df = pd.read_csv('my_secret_file.csv')  # ideally with lots of parsing options

    Більшість даних є власником, ми отримуємо це: Складіть подібні дані і подивіться, чи можете ви відтворити проблему (щось невелике).

  • не пояснюйте ситуацію смутно словами, як у вас є DataFrame, який є "великим", згадуйте деякі імена стовпців попутно (не забудьте згадати їхні типи). Спробуйте детально розглянути щось, що є абсолютно безглуздим, не бачачи реального контексту. Імовірно, ніхто навіть не збирається читати до кінця цього параграфа.

    Нариси погані, простіше з невеликими прикладами.

  • не включайте 10+ (100+ ??) рядків обміну даними, перш ніж перейти до власного питання.

    Будь ласка, цього ми достатньо бачимо в наших робочих днях. Ми хочемо допомогти, але НЕ так, як це ... .
    Виріжте вступ і просто покажіть відповідні DataFrames (або невеликі їх версії) на кроці, який заподіює вам проблеми.

У будь-якому разі, весело вивчаючи Python, NumPy та Pandas!


30
+1 для pd.read_clipboard(sep='\s\s+')підказки. Коли я публікую запитання ТА, для яких потрібен спеціальний, але легко поділяється кадр даних, як-от цей, я будую його у excel, скопіюйте його в буфер обміну, а потім доручу SOERS робити те саме. Економить стільки часу!
zelusp

1
pd.read_clipboard(sep='\s\s+')пропозиція , здається, не працювати , якщо ви використовуєте Python на віддаленому сервері, який де багато великих наборів даних жити.
user5359531

1
Чому pd.read_clipboard(sep='\s\s+'), а не простішою pd.read_clipboard()(за замовчуванням ‘s+’)? Першої необхідності , по крайней мере 2 пробільних символів, які можуть викликати проблеми , якщо є тільки один (наприклад , див , наприклад в @JohnE «s відповідь ).
MarianD

3
@MarianD Причина того, що \ s \ s + є настільки популярною, полягає в тому, що в назві стовпців часто є одне, наприклад, кілька разів рідше, і вихід панд добре розміщується щонайменше у двох між стовпцями. Оскільки це лише для іграшок / невеликих наборів даних, це досить потужно / у більшості випадків. Примітка: розділені вкладки були б іншою історією, хоча stackoverflow замінює вкладки пробілами, але якщо у вас є tsv, просто використовуйте \ t.
Енді Хайден

3
Тьфу, я завжди використовую pd.read_clipboard(), коли вони пробіли, я роблю pd.read_clipboard(sep='\s+{2,}', engine='python'):: P
U10-Вперед

72

Як створити вибіркові набори даних

Це головним чином для розширення відповіді @ AndyHayden, надаючи приклади того, як можна створити зразки фреймів даних. Панди і (особливо) numpy дають вам різноманітні інструменти для цього, щоб ви могли загалом створити розумний факсимільний файл будь-якого реального набору даних лише за допомогою декількох рядків коду.

Після імпорту нуме і панди не забудьте надати випадкове насіння, якщо ви хочете, щоб люди змогли точно відтворити ваші дані та результати.

import numpy as np
import pandas as pd

np.random.seed(123)

Приклад кухонної мийки

Ось приклад, що показує різноманітні речі, які ви можете зробити. Усі види корисних фреймворків даних можуть бути створені з підмножини цього:

df = pd.DataFrame({ 

    # some ways to create random data
    'a':np.random.randn(6),
    'b':np.random.choice( [5,7,np.nan], 6),
    'c':np.random.choice( ['panda','python','shark'], 6),

    # some ways to create systematic groups for indexing or groupby
    # this is similar to r's expand.grid(), see note 2 below
    'd':np.repeat( range(3), 2 ),
    'e':np.tile(   range(2), 3 ),

    # a date range and set of random dates
    'f':pd.date_range('1/1/2011', periods=6, freq='D'),
    'g':np.random.choice( pd.date_range('1/1/2011', periods=365, 
                          freq='D'), 6, replace=False) 
    })

Це дає:

          a   b       c  d  e          f          g
0 -1.085631 NaN   panda  0  0 2011-01-01 2011-08-12
1  0.997345   7   shark  0  1 2011-01-02 2011-11-10
2  0.282978   5   panda  1  0 2011-01-03 2011-10-30
3 -1.506295   7  python  1  1 2011-01-04 2011-09-07
4 -0.578600 NaN   shark  2  0 2011-01-05 2011-02-27
5  1.651437   7  python  2  1 2011-01-06 2011-02-03

Деякі примітки:

  1. np.repeatі np.tile(стовпці dта e) дуже корисні для створення груп та індексів дуже регулярним чином. Для двох стовпців це можна використовувати для легкого дублювання r, expand.grid()але також є більш гнучким у здатності забезпечити підмножину всіх перестановок. Однак для 3 і більше стовпців синтаксис швидко стає непростим.
  2. Для більш прямої заміни на r expand.grid()див. itertoolsРішення в кулінарній книзі панди або в np.meshgridнаведеному тут розчині . Вони дозволять отримати будь-яку кількість розмірів.
  3. З цим можна зробити зовсім небагато np.random.choice. Наприклад, у колонці у gнас є випадковий вибір з 6 дат 2011 року. Крім того, встановивши, replace=Falseми можемо впевнитись, що ці дати є унікальними - дуже зручно, якщо ми хочемо використовувати це як індекс з унікальними значеннями.

Підроблені дані фондового ринку

Окрім прийняття підмножини вищевказаного коду, ви можете додатково комбінувати методи, що дозволяють робити майже все. Наприклад, ось короткий приклад, який поєднує np.tileта date_rangeстворює зразкові тикерні дані для 4 акцій, що охоплюють ті самі дати:

stocks = pd.DataFrame({ 
    'ticker':np.repeat( ['aapl','goog','yhoo','msft'], 25 ),
    'date':np.tile( pd.date_range('1/1/2011', periods=25, freq='D'), 4 ),
    'price':(np.random.randn(100).cumsum() + 10) })

Зараз у нас є вибірковий набір даних зі 100 рядками (25 дат на тикер), але ми використали лише 4 рядки для цього, що спрощує відтворення всіх інших без копіювання та вставлення 100 рядків коду. Потім ви можете відобразити підмножини даних, якщо це допоможе пояснити ваше питання:

>>> stocks.head(5)

        date      price ticker
0 2011-01-01   9.497412   aapl
1 2011-01-02  10.261908   aapl
2 2011-01-03   9.438538   aapl
3 2011-01-04   9.515958   aapl
4 2011-01-05   7.554070   aapl

>>> stocks.groupby('ticker').head(2)

         date      price ticker
0  2011-01-01   9.497412   aapl
1  2011-01-02  10.261908   aapl
25 2011-01-01   8.277772   goog
26 2011-01-02   7.714916   goog
50 2011-01-01   5.613023   yhoo
51 2011-01-02   6.397686   yhoo
75 2011-01-01  11.736584   msft
76 2011-01-02  11.944519   msft

2
Чудова відповідь. Після написання цього питання я насправді написав дуже коротку, просту реалізацію, expand.grid()що міститься в кулінарній книзі панд , ви можете включити це і у свою відповідь. Ваша відповідь показує, як створити складніші набори даних, ніж мої expand_grid()функції можуть впоратися, що чудово.
Маріус

46

Щоденник відповіді

Моя найкраща порада щодо запитань - це грати на психологію людей, які відповідають на питання. Будучи одним із цих людей, я можу зрозуміти, чому я відповідаю на певні запитання та чому я не відповідаю на інші.

Мотивації

Мені мотивовано відповідати на питання з кількох причин

  1. Stackoverflow.com був надзвичайно цінним ресурсом для мене. Я хотів повернути.
  2. У своїх зусиллях повернутись, я знайшов цей сайт ще більш потужним ресурсом, ніж раніше. Відповідати на питання - це досвід навчання для мене, і я люблю вчитися. Прочитайте цю відповідь та прокоментуйте іншого ветеринара . Така взаємодія робить мене щасливим.
  3. Мені подобаються очки!
  4. Див. №3.
  5. Мені подобаються цікаві проблеми.

Усі мої найчистіші наміри великі і всі, але я отримую це задоволення, якщо я відповім на 1 запитання або 30. Що сприяє моєму вибору, на які питання відповісти, має величезний компонент максимізації балів.

Я також витрачу час на цікаві проблеми, але це мало і далеко між собою, і це не допоможе запитувачеві, якому потрібно вирішити нецікаве питання. Ваша найкраща ставка, щоб змусити мене відповісти на питання, - це подати питання на блюді, щоб я відповів на нього якомога менше зусиль. Якщо я переглядаю два запитання, а в одного є код, я можу скопіювати пасту, щоб створити всі потрібні мені змінні ... Я беру це! Я повернусь до іншого, якщо встигну.

Основні поради

Спростіть відповіді людей на запитання.

  • Надайте код, який створює необхідні змінні.
  • Мінімізуйте цей код. Якщо мені очі заглядають під час перегляду поста, я переходжу до наступного питання або повертаюся до того, що я ще роблю.
  • Подумайте, що ви просите, і будьте конкретні. Ми хочемо побачити, що ви зробили, оскільки природні мови (англійська) неточні та заплутані. Зразки коду того, що ви спробували, допоможуть усунути невідповідності в описі натуральної мови.
  • БУДЬ ласка, покажіть, що ви очікуєте !!! Я повинен сісти і спробувати речі. Я майже ніколи не знаю відповіді на запитання, не намагаючись вияснити деякі речі. Якщо я не бачу приклад того, що ви шукаєте, я можу перейти до питання, тому що мені не здається здогадуватися.

Ваша репутація - це більше, ніж просто ваша репутація.

Мені подобаються моменти (я згадував вище). Але ці моменти насправді не є моєю репутацією. Моя справжня репутація - це об'єднання того, що про мене думають інші на сайті. Я прагну бути справедливим і чесним і сподіваюся, що це можуть побачити і інші. Що це означає для запитувача, ми пам’ятаємо поведінку запитуючих. Якщо ви не вибираєте відповіді і не підтримуєте гарні відповіді, я пам'ятаю. Якщо ти поводиш себе так, як мені не подобається, або я люблю те, як мені подобається. Це також грає, на які питання я відповім.


У будь-якому випадку я, мабуть, можу продовжувати, але я шкодую всіх вас, хто насправді це читав.


26

Виклик Одним із найскладніших аспектів відповіді на питання ЗО є час, необхідний для відновлення проблеми (включаючи дані). На запитання, які не мають чіткого способу відтворення даних, менше шансів відповісти. Зважаючи на те, що ви витрачаєте час, щоб написати запитання, і у вас є питання, з яким ви хочете допомогти, ви можете легко допомогти собі, надавши дані, які потім можуть використовувати інші, щоб вирішити вашу проблему.

Інструкції, надані @Andy для написання гарних запитань щодо Pandas - це чудове місце для початку. Для отримання додаткової інформації див. Запитання та створення мінімальних, повних та перевірених прикладів .

Будь ласка, чітко сформулюйте своє запитання наперед. Виділивши час, щоб написати своє запитання та будь-який зразок коду, спробуйте його прочитати та надайте для свого читача «Резюме», який узагальнює проблему та чітко заявляє питання.

Оригінальне запитання :

У мене є ці дані ...

Я хочу це зробити ...

Я хочу, щоб мій результат виглядав так ...

Однак, коли я намагаюся зробити [це], у мене виникає така проблема ...

Я намагався знайти рішення, зробивши [це] та [це].

Як це виправити?

Залежно від кількості даних, зразкового коду та наборів помилок, читачеві потрібно пройти довгий шлях, перш ніж зрозуміти, у чому проблема. Спробуйте перевстановити своє запитання, щоб воно було зверху, а потім вкажіть необхідні деталі.

Переглянуте питання :

Питання: Як я можу зробити це [це]?

Я намагався знайти рішення, зробивши [це] та [це].

Коли я намагаюся зробити [це], у мене виникає така проблема ...

Я хотів би, щоб мої кінцеві результати виглядали так ...

Ось мінімальний код, який може відтворити мою проблему ...

Ось як відтворити мої вибіркові дані: df = pd.DataFrame({'A': [...], 'B': [...], ...})

НАДАЙТЕ Зразки даних, якщо це потрібно !!!

Іноді лише голова або хвіст DataFrame - це все, що потрібно. Ви також можете використовувати методи, запропоновані @JohnE, для створення більших наборів даних, які можуть бути відтворені іншими. Використовуючи його приклад для створення 100 рядкових цін на акції DataFrame:

stocks = pd.DataFrame({ 
    'ticker':np.repeat( ['aapl','goog','yhoo','msft'], 25 ),
    'date':np.tile( pd.date_range('1/1/2011', periods=25, freq='D'), 4 ),
    'price':(np.random.randn(100).cumsum() + 10) })

Якщо це були ваші фактичні дані, ви можете просто включити голову та / або хвіст фрейму даних наступним чином (не забудьте анонімізувати будь-які конфіденційні дані):

>>> stocks.head(5).to_dict()
{'date': {0: Timestamp('2011-01-01 00:00:00'),
  1: Timestamp('2011-01-01 00:00:00'),
  2: Timestamp('2011-01-01 00:00:00'),
  3: Timestamp('2011-01-01 00:00:00'),
  4: Timestamp('2011-01-02 00:00:00')},
 'price': {0: 10.284260107718254,
  1: 11.930300761831457,
  2: 10.93741046217319,
  3: 10.884574289565609,
  4: 11.78005850418319},
 'ticker': {0: 'aapl', 1: 'aapl', 2: 'aapl', 3: 'aapl', 4: 'aapl'}}

>>> pd.concat([stocks.head(), stocks.tail()], ignore_index=True).to_dict()
{'date': {0: Timestamp('2011-01-01 00:00:00'),
  1: Timestamp('2011-01-01 00:00:00'),
  2: Timestamp('2011-01-01 00:00:00'),
  3: Timestamp('2011-01-01 00:00:00'),
  4: Timestamp('2011-01-02 00:00:00'),
  5: Timestamp('2011-01-24 00:00:00'),
  6: Timestamp('2011-01-25 00:00:00'),
  7: Timestamp('2011-01-25 00:00:00'),
  8: Timestamp('2011-01-25 00:00:00'),
  9: Timestamp('2011-01-25 00:00:00')},
 'price': {0: 10.284260107718254,
  1: 11.930300761831457,
  2: 10.93741046217319,
  3: 10.884574289565609,
  4: 11.78005850418319,
  5: 10.017209045035006,
  6: 10.57090128181566,
  7: 11.442792747870204,
  8: 11.592953372130493,
  9: 12.864146419530938},
 'ticker': {0: 'aapl',
  1: 'aapl',
  2: 'aapl',
  3: 'aapl',
  4: 'aapl',
  5: 'msft',
  6: 'msft',
  7: 'msft',
  8: 'msft',
  9: 'msft'}}

Ви також можете надати опис DataFrame (використовуючи лише відповідні стовпці). Це полегшує іншим перевірити типи даних кожного стовпця та виявити інші поширені помилки (наприклад, дати як рядок проти datetime64 та об’єкт):

stocks.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 100 entries, 0 to 99
Data columns (total 3 columns):
date      100 non-null datetime64[ns]
price     100 non-null float64
ticker    100 non-null object
dtypes: datetime64[ns](1), float64(1), object(1)

ПРИМІТКА. Якщо у вашому DataFrame є MultiIndex:

Якщо у вашій DataFrame є багатоіндекс, ви повинні спочатку скинути його перед тим, як зателефонувати to_dict. Потім потрібно відтворити індекс за допомогою set_index:

# MultiIndex example.  First create a MultiIndex DataFrame.
df = stocks.set_index(['date', 'ticker'])
>>> df
                       price
date       ticker           
2011-01-01 aapl    10.284260
           aapl    11.930301
           aapl    10.937410
           aapl    10.884574
2011-01-02 aapl    11.780059
...

# After resetting the index and passing the DataFrame to `to_dict`, make sure to use 
# `set_index` to restore the original MultiIndex.  This DataFrame can then be restored.

d = df.reset_index().to_dict()
df_new = pd.DataFrame(d).set_index(['date', 'ticker'])
>>> df_new.head()
                       price
date       ticker           
2011-01-01 aapl    10.284260
           aapl    11.930301
           aapl    10.937410
           aapl    10.884574
2011-01-02 aapl    11.780059

12

Ось моя версія dput- стандартного інструменту R для створення відтворюваних звітів - для Pandas DataFrames. Ймовірно, це не вдасться для складніших кадрів, але, схоже, це робиться в простих випадках:

import pandas as pd
def dput (x):
    if isinstance(x,pd.Series):
        return "pd.Series(%s,dtype='%s',index=pd.%s)" % (list(x),x.dtype,x.index)
    if isinstance(x,pd.DataFrame):
        return "pd.DataFrame({" + ", ".join([
            "'%s': %s" % (c,dput(x[c])) for c in x.columns]) + (
                "}, index=pd.%s)" % (x.index))
    raise NotImplementedError("dput",type(x),x)

тепер,

df = pd.DataFrame({'a':[1,2,3,4,2,1,3,1]})
assert df.equals(eval(dput(df)))
du = pd.get_dummies(df.a,"foo")
assert du.equals(eval(dput(du)))
di = df
di.index = list('abcdefgh')
assert di.equals(eval(dput(di)))

Зауважте, що це дає набагато більш багатослівний вихід, ніж DataFrame.to_dict, наприклад,

pd.DataFrame({
  'foo_1':pd.Series([1, 0, 0, 0, 0, 1, 0, 1],dtype='uint8',index=pd.RangeIndex(start=0, stop=8, step=1)),
  'foo_2':pd.Series([0, 1, 0, 0, 1, 0, 0, 0],dtype='uint8',index=pd.RangeIndex(start=0, stop=8, step=1)),
  'foo_3':pd.Series([0, 0, 1, 0, 0, 0, 1, 0],dtype='uint8',index=pd.RangeIndex(start=0, stop=8, step=1)),
  'foo_4':pd.Series([0, 0, 0, 1, 0, 0, 0, 0],dtype='uint8',index=pd.RangeIndex(start=0, stop=8, step=1))},
  index=pd.RangeIndex(start=0, stop=8, step=1))

проти

{'foo_1': {0: 1, 1: 0, 2: 0, 3: 0, 4: 0, 5: 1, 6: 0, 7: 1}, 
 'foo_2': {0: 0, 1: 1, 2: 0, 3: 0, 4: 1, 5: 0, 6: 0, 7: 0}, 
 'foo_3': {0: 0, 1: 0, 2: 1, 3: 0, 4: 0, 5: 0, 6: 1, 7: 0}, 
 'foo_4': {0: 0, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0, 6: 0, 7: 0}}

для duвище, але він зберігає типи стовпців . Наприклад, у наведеному вище тестовому випадку,

du.equals(pd.DataFrame(du.to_dict()))
==> False

бо du.dtypesє uint8і pd.DataFrame(du.to_dict()).dtypesє int64.


це зрозуміліше, хоча я визнаю, що не бачу, чому я хотів би скористатися цимto_dict
Пол H,

2
Тому що він зберігає типи стовпців. Більш конкретно, du.equals(eval(dput(df))).
sds
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.