{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "2a419685-e6e4-4626-95e2-6a7c7c9f7411", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "# ensure that all columns are shown and that colum content is not cut\n", "pd.set_option('display.max_columns', None)\n", "pd.set_option('display.max_colwidth', None)\n", "pd.set_option('display.width',1000)\n", "pd.set_option('display.max_rows', 500) # ensure that all rows are shown" ] }, { "cell_type": "markdown", "id": "439fd62c-ebd8-4a93-9309-0b120d26d5f9", "metadata": {}, "source": [ "# Customize Standardizer\n", "This Notebook gives some ideas how you could customize the standardizer classes.\n", "\n", "All three standardizer classes `BalanceSheetStandardizer`, `IncomeStatementStandardizer`, and `CashFlowStandardizer` are derived from the same base class `Standardizer` and share the same constructor parameters. In fact, the whole behavior of the standardizer is defined by these parameters and the three standardizer classes are just containers which define the values for the constructor parameters but do not define additional methods or overwrite existing methods. So, it is simply a configuration of the base class.\n", "\n", "Since every constructor parameter can be overwritten when instantiating one of the three standardizer classes, you can customize the standardizer in three ways:\n", "\n", "1. Simply adapt the parameters of the constructor when you instantiate `BalanceSheetStandardizer`, `IncomeStatementStandardizer`, or `CashFlowStandardizer`. A simply way, for instance, to adapt the list of tags/columns that should appear in the final result.\n", "2. Create a sublcass of `BalanceSheetStandardizer`, `IncomeStatementStandardizer`, or `CashFlowStandardizer` and redefine certain, more complex rules. For instance, maybe you want to define additional `Validation` rules, or you want to change the `Post` rules so that NaN-values are not set to zero but instead stay undefined.\n", "3. Create a subclass directly from `Standardizer` and define everything yourself." ] }, { "cell_type": "markdown", "id": "985ed12d-8465-46f1-9cfb-7580fb82bd46", "metadata": { "tags": [] }, "source": [ "==========================================================\n", "\n", "**If you find this tool useful, a sponsorship would be greatly appreciated!**\n", "\n", "**https://github.com/sponsors/HansjoergW**\n", "\n", "How to get in touch\n", "\n", "* Found a bug: https://github.com/HansjoergW/sec-fincancial-statement-data-set/issues\n", "* Have a remark: https://github.com/HansjoergW/sec-fincancial-statement-data-set/discussions/categories/general\n", "* Have an idea: https://github.com/HansjoergW/sec-fincancial-statement-data-set/discussions/categories/ideas\n", "* Have a question: https://github.com/HansjoergW/sec-fincancial-statement-data-set/discussions/categories/q-a\n", "* Have something to show: https://github.com/HansjoergW/sec-fincancial-statement-data-set/discussions/categories/show-and-tell\n", "\n", "==========================================================" ] }, { "cell_type": "markdown", "id": "2189f407-0003-44db-b957-21ed3c79908e", "metadata": {}, "source": [ "## Basic Constructor Parameters\n", "\n", "The following simple bascic constructors are available to change some details of the behavior." ] }, { "cell_type": "markdown", "id": "363a3735-a1bd-4343-9a71-8c955aaec5c3", "metadata": {}, "source": [ "### `filter_for_main_statement`\n", "\n", "A quaterly or annual report usually contains many different tables with data. Beside the tables with the primary financial information (Balance Sheet, Income Statement, or the CashFlow) there tables that often contain part of the information from the primary financial statements. Usually, however, you are just interested in the tables that contain the primary financial information.\n", "\n", "If this flag is set to true (which is the default value), only the table that contains most data points that generally belong to the appropriate statement, will be returned in the result set." ] }, { "cell_type": "markdown", "id": "e33ab955-dfa3-4e07-a24f-c71a834f2b0c", "metadata": {}, "source": [ "### `additional_final_sub_fields`\n", "\n", "When you call the `process` method of a standardizer, you will receive a restulting dataframe that just contains the `adsh` column as an identifier. In contrary, when you use the `present` method, the resulting data frame is enriched with additional information from the sub_df. By default, these are the columns `cik`, `name` (the last registered name of the company), `form` (either 10-K or 10Q), `fye` (the financial year ending as MMDD), `fy` (the financial year to which the report belongs), `fp` (the financial period Q1, Q2, Q3, or FY), `filed` (date when the report was filed with the SEC as an integer value in the format YYYYMMDD), `data` (same as `filed` but as areal date format).\n", "\n", "However, there are many more columns in the sub_df available (like contact information). So if you would like to have the zip code of the town where the company is based, you can define this with the `additional_final_sub_fields` parameter:\n", "\n", " bs_standardizer = BalanceSheetStandardizer(additional_final_sub_fields=['zipba'])\n", "\n", " result_df = bs_standardizer.present(joined_bag)\n", " \n", " # or via the get_standardize_bag\n", " bs_standardizer.get_standardize_bag().result_df\n" ] }, { "cell_type": "markdown", "id": "805fca3c-abb3-47ce-9d19-dacaaf9e8fb5", "metadata": {}, "source": [ "### `additional_final_tags`\n", "\n", "Every standardizer defines an internal list `final_tags` which defines the tags (resp. the columns) that are contained in the data frame that is returned. This columns are only a subset and sometimes aggregated fields of the fields that actually are avaiable. As the name standardizer suggest, the goal is to just provide information that is available in most of the reports. \n", "\n", "There may be situations, when you would like to have additional tags returned as well. For instance, instead of just having `LiabilitiesNoncurrent`, you might also be interested in the `LongTermDebt`. This is possible by defining the `additional_final_tags` parameter:\n", "\n", "\n", " bs_standardizer = BalanceSheetStandardizer(additional_final_tags=['LongTermDebt'])\n", "\n", " result_df = bs_standardizer.present(joined_bag)\n", " \n", " # or via the get_standardize_bag\n", " bs_standardizer.get_standardize_bag().result_df" ] }, { "cell_type": "markdown", "id": "54bb12a5-7a4d-474b-9acf-9cad968d1ab8", "metadata": {}, "source": [ "### `final_tags`\n", "\n", "Instead of just adding additional final tags with the `additional_final_tags` parameter, you can redefine the whole list directly with `final_tags` parameter. For instance, if you want to remove certain tags from the final result, or if you want them to appear in a certain order.\n", "\n", " # The default list is\n", " # ['Assets', 'AssetsCurrent', 'Cash', 'AssetsNoncurrent',\n", " # 'Liabilities', 'LiabilitiesCurrent', 'LiabilitiesNoncurrent',\n", " # 'Equity',\n", " # 'HolderEquity',\n", " # 'RetainedEarnings',\n", " # 'AdditionalPaidInCapital',\n", " # 'TreasuryStockValue',\n", " # 'TemporaryEquity',\n", " # 'RedeemableEquity',\n", " # 'LiabilitiesAndEquity'] \n", " # However, we are only interested in a subset of it and in a different order, so we adapt final_tags\n", " bs_standardizer = BalanceSheetStandardizer(final_tags=['LiabilitiesCurrent', 'LiabilitiesNoncurrent', 'Liabilities', 'AssetsCurrent', 'AssetsNoncurrent', 'Assets'])\n", "\n", " result_df = bs_standardizer.present(joined_bag)\n", " \n", " # or via the get_standardize_bag\n", " bs_standardizer.get_standardize_bag().result_df" ] }, { "cell_type": "markdown", "id": "1b7e8914-a708-489d-b227-10bdb3b8c1a4", "metadata": {}, "source": [ "## Subclassing\n", "\n", "Subclassing makes sense when you want to change the more complex parameters. For instance, the definition of rules. Of course, you could also directly do that just by changing the constructor parameter as explained above, but it might make more sense to encapsulate more complex definitions within a special class.\n", "\n", "The following example shows, how we could change the definition of the `Post` rules, so that unset values are not set to zero at the end of the process in the `BalanceSheetStandardizer`. So, we simply remove all `PostSetToZero` entries from the original definition of the `post_rule_tree`.\n", "\n", " class NoSetToZeroBalanceSheetStandardizer(BalanceSheetStandardizer):\n", " \n", " # redefined post_rule_tree without any PostSetToZero rules\n", " post_rule_tree = RuleGroup(prefix=\"BS_POST\",\n", " rules=[\n", " # if only Assets is sets, set the AssetsCurrent to value\n", " # of Assets and AssetsNoncurrent to 0\n", " PostCopyToFirstSummand(sum_tag='Assets',\n", " first_summand='AssetsCurrent',\n", " other_summands=[\n", " 'AssetsNoncurrent']),\n", " # if only Liabilities is sets, set the LiabilitiesCurrent to\n", " # value of Liabilities and LiabilitiesNoncurrent to 0\n", " PostCopyToFirstSummand(sum_tag='Liabilities',\n", " first_summand='LiabilitiesCurrent',\n", " other_summands=[\n", " 'LiabilitiesNoncurrent']),\n", " ])\n", " \n", " def __init__():\n", " super().__init__(\n", " post_rule_tree=post_rule_tree\n", " )\n", " \n", " \n", " \n" ] }, { "cell_type": "markdown", "id": "9a143eb7-3ef2-4401-baee-68eee0abf1bf", "metadata": {}, "source": [ "# How to Find Tags\n", "\n", "A crucial thing when standardizing reports is to know or to find out which tags we care about. \n", "\n", "As mentioned in other places, many different tags can be used in a report, some have a similar meaning, a lot of them have a hierachical order, sometimes some of them are misused, and so on. \n", "\n", "Since we actually have all the data for a certain report (for instance the balance sheet) it is quite esay to get a first impression about the number of tags and how often they are used. In order to analyze, we use the data set that was produced in the notebook 06_bulk_data_processing_deep_dive.ipynb" ] }, { "cell_type": "code", "execution_count": 4, "id": "95b50b70-666a-46b1-a996-a52f17b0b435", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "loading data ...\n" ] } ], "source": [ "from secfsdstools.d_container.databagmodel import JoinedDataBag\n", "\n", "print(\"loading data ...\")\n", "all_bs_joinedbag:JoinedDataBag = JoinedDataBag.load(target_path=\"set/parallel/BS/joined\")" ] }, { "cell_type": "markdown", "id": "8872301b-1625-43c1-b5eb-bc704f441871", "metadata": {}, "source": [ "First, lets see how many tags there are in total and how often they seem to be used" ] }, { "cell_type": "code", "execution_count": 5, "id": "fe71f11d-a1a7-4b92-8228-970d5e9ba013", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "number of different tags used in a BalanceSheets: 3148\n" ] } ], "source": [ "print(\"number of different tags used in a BalanceSheets:\", len(all_bs_joinedbag.pre_num_df.tag.unique()))" ] }, { "cell_type": "markdown", "id": "a42f4e75-f583-45eb-84d9-db4b369f3213", "metadata": {}, "source": [ "Now, an average balance sheet has about 20 to 30 positions and in this data set we have about 320'000 balance sheets. As you can imagine, there is a lot if \"individuality\" in these reports.\n", "\n", "So, let's find out, which are the more \"common\" tags that are used." ] }, { "cell_type": "code", "execution_count": 6, "id": "0b5b72dc-2d78-48bd-a8ad-1d5c0ea7d011", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "StockholdersEquity 1110491\n", "Assets 709747\n", "StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest 575385\n", "LiabilitiesAndStockholdersEquity 394086\n", "InvestmentOwnedAtFairValue 389453\n", "PropertyPlantAndEquipmentNet 375284\n", "CommonStockSharesOutstanding 371743\n", "CashAndCashEquivalentsAtCarryingValue 369591\n", "CommonStockSharesIssued 366020\n", "Goodwill 356448\n", "CommonStockValue 351803\n", "Liabilities 348181\n", "CommonStockSharesAuthorized 348130\n", "InvestmentOwnedAtCost 336960\n", "CommonStockParOrStatedValuePerShare 335472\n", "RetainedEarningsAccumulatedDeficit 329198\n", "AvailableForSaleSecuritiesDebtSecurities 318473\n", "AssetsCurrent 304570\n", "LiabilitiesCurrent 302623\n", "AccumulatedOtherComprehensiveIncomeLossNetOfTax 300826\n", "AvailableForSaleSecurities 264330\n", "PreferredStockSharesAuthorized 236841\n", "PreferredStockValue 225513\n", "AccountsPayableCurrent 217727\n", "PreferredStockSharesIssued 216400\n", "LoansAndLeasesReceivableNetOfDeferredIncome 211806\n", "PreferredStockParOrStatedValuePerShare 211325\n", "CommitmentsAndContingencies 207112\n", "AccountsReceivableNetCurrent 205625\n", "PreferredStockSharesOutstanding 202550\n", "OtherAssetsNoncurrent 200911\n", "AdditionalPaidInCapital 181274\n", "InventoryNet 171734\n", "IntangibleAssetsNetExcludingGoodwill 168389\n", "OtherLiabilitiesNoncurrent 159061\n", "AccruedLiabilitiesCurrent 144543\n", "PrepaidExpenseAndOtherAssetsCurrent 137482\n", "LongTermDebtNoncurrent 132520\n", "AdditionalPaidInCapitalCommonStock 131248\n", "LoansAndLeasesReceivableAllowance 119182\n", "LoansAndLeasesReceivableGrossCarryingAmount 119165\n", "FinancingReceivableExcludingAccruedInterestBeforeAllowanceForCreditLoss 108309\n", "FiniteLivedIntangibleAssetsNet 105873\n", "MinorityInterest 100242\n", "TreasuryStockValue 98472\n", "OtherAssets 91265\n", "OtherAssetsCurrent 88988\n", "NotesReceivableGross 88965\n", "LongTermDebt 87933\n", "OperatingLeaseRightOfUseAsset 85842\n", "Name: tag, dtype: int64" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "counts_df = all_bs_joinedbag.pre_num_df.tag.value_counts()\n", "\n", "counts_df.head(50)" ] }, { "cell_type": "markdown", "id": "d50656f1-50e8-49da-9b81-d34010cbc99c", "metadata": {}, "source": [ "Maybe we are interested in a certain group of tags, like Assets. So lets see what tags we have that have \"Assets\" in their name:" ] }, { "cell_type": "code", "execution_count": 7, "id": "0d50df18-1f56-424f-a6c3-f726298080a4", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "Assets 709747\n", "AssetsCurrent 304570\n", "OtherAssetsNoncurrent 200911\n", "IntangibleAssetsNetExcludingGoodwill 168389\n", "PrepaidExpenseAndOtherAssetsCurrent 137482\n", "FiniteLivedIntangibleAssetsNet 105873\n", "OtherAssets 91265\n", "OtherAssetsCurrent 88988\n", "DerivativeAssets 44517\n", "DeferredTaxAssetsNetCurrent 36946\n", "DeferredIncomeTaxAssetsNet 35487\n", "FiniteLivedIntangibleAssetsAccumulatedAmortization 34219\n", "DeferredTaxAssetsNetNoncurrent 31841\n", "RegulatoryAssetsNoncurrent 30960\n", "AssetsNoncurrent 24428\n", "PrepaidExpenseAndOtherAssets 18703\n", "AssetsOfDisposalGroupIncludingDiscontinuedOperationCurrent 17427\n", "DerivativeAssetsCurrent 16088\n", "SeparateAccountAssets 13668\n", "RegulatoryAssetsCurrent 12606\n", "DepositsAssetsNoncurrent 12044\n", "DerivativeAssetsNoncurrent 11716\n", "AssetsHeldInTrustNoncurrent 11390\n", "IntangibleAssetsNetIncludingGoodwill 11171\n", "DeferredTaxAssetsLiabilitiesNetNoncurrent 10513\n", "OtherIntangibleAssetsNet 9503\n", "OtherRealEstateAndForeclosedAssets 8885\n", "AssetsOfDisposalGroupIncludingDiscontinuedOperation 6603\n", "DeferredTaxAssetsNet 6397\n", "DeferredTaxAssetsLiabilitiesNetCurrent 6357\n", "IndefiniteLivedIntangibleAssetsExcludingGoodwill 5783\n", "DeferredTaxAssetsLiabilitiesNet 5620\n", "OtherAssetsFairValueDisclosure 5305\n", "DisposalGroupIncludingDiscontinuedOperationAssetsNoncurrent 5132\n", "DefinedBenefitPlanAssetsForPlanBenefitsNoncurrent 5123\n", "ForeclosedAssets 4890\n", "AssetsHeldForSaleNotPartOfDisposalGroupCurrent 4572\n", "DecommissioningTrustAssetsAmount 4535\n", "DepositsAssetsCurrent 4263\n", "VariableInterestEntityConsolidatedCarryingAmountAssets 3903\n", "AssetsNet 3600\n", "DeferredCompensationPlanAssets 3349\n", "AssetsHeldForSaleCurrent 3289\n", "DepositAssets 3278\n", "DepositsAssets 3224\n", "AssetsHeldForSaleNotPartOfDisposalGroup 2836\n", "InvestmentsAndOtherNoncurrentAssets 2576\n", "AssetsSoldUnderAgreementsToRepurchaseRepurchaseLiability 2563\n", "OtherAssetsMiscellaneousNoncurrent 2488\n", "FiniteLivedIntangibleAssetsGross 2420\n", "Name: tag, dtype: int64" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "all_bs_joinedbag.pre_num_df.tag[all_bs_joinedbag.pre_num_df.tag.str.contains(\"Assets\")].value_counts()[:50] # only show the top 50" ] }, { "cell_type": "markdown", "id": "43c9cbe2-75fa-4dbd-816b-b871b8f07886", "metadata": {}, "source": [ "Another great tool to get a better understanding about the tag hierarchy is ChatGPT. For instance, try out a prompt like the following:\n", "\n", "*Please visualize the hierarchy of the 50 most common xbrl tags in a balance sheet that belong to \"Assets\". Please create the visualization with ASCII characters only.*\n", "\n", "Result:\n", "\n", " Total Assets\n", " │\n", " ├── Current Assets\n", " │ ├── Cash and Cash Equivalents\n", " │ │ ├── CashAndCashEquivalentsAtCarryingValue\n", " │ │ ├── CashAndCashEquivalentsRestrictedCashAndRestrictedCashEquivalents\n", " │ │ └── CashAndCashEquivalentsAtCarryingValueIncludingDiscontinuedOperations\n", " │ ├── Receivables\n", " │ │ ├── AccountsReceivableNetCurrent\n", " │ │ └── ReceivablesNetCurrent\n", " │ ├── Inventories\n", " │ │ ├── InventoryNet\n", " │ │ └── InventoriesFinishedGoodsNetOfReserves\n", " │ ├── Prepaid Expenses\n", " │ │ └── PrepaidExpenseAndOtherAssetsCurrent\n", " │ └── Other Current Assets\n", " │ ├── OtherAssetsCurrent\n", " │ └── AssetsHeldForSaleCurrent\n", " │\n", " ├── Non-Current Assets\n", " │ ├── Property, Plant, and Equipment (PP&E)\n", " │ │ ├── PropertyPlantAndEquipmentNet\n", " │ │ └── LandAndBuildingsNetOfAccumulatedDepreciation\n", " │ ├── Intangible Assets\n", " │ │ ├── Goodwill\n", " │ │ └── IntangibleAssetsNetExcludingGoodwill\n", " │ ├── Investments\n", " │ │ ├── InvestmentsAndAdvances\n", " │ │ └── InvestmentsNoncurrent\n", " │ ├── Deferred Tax Assets\n", " │ │ └── DeferredTaxAssetsNoncurrent\n", " │ └── Other Non-Current Assets\n", " │ ├── OtherAssetsNoncurrent\n", " │ ├── OtherIntangibleAssetsNet\n", " │ └── AssetsHeldForSaleNoncurrent\n", " │\n", " ├── Restricted Assets\n", " │ ├── RestrictedCashAndCashEquivalentsNoncurrent\n", " │ ├── RestrictedCashAndCashEquivalentsAtCarryingValue\n", " │ └── RestrictedCashAndInvestmentsNoncurrent\n", " │\n", " ├── Financial Instruments\n", " │ ├── MarketableSecurities\n", " │ │ ├── MarketableSecuritiesCurrent\n", " │ │ └── MarketableSecuritiesNoncurrent\n", " │ ├── AvailableForSaleSecurities\n", " │ │ ├── AvailableForSaleSecuritiesCurrent\n", " │ │ └── AvailableForSaleSecuritiesNoncurrent\n", " │ └── TradingSecurities\n", " │ ├── TradingSecuritiesCurrent\n", " │ └── TradingSecuritiesNoncurrent\n", " │\n", " └── Other Assets\n", " ├── OtherAssets\n", " ├── MiscellaneousAssets\n", " └── DerivativeAssets\n", " ├── DerivativeAssetsCurrent\n", " └── DerivativeAssetsNoncurrent\n", "\n", "\n", "Is the tag `RestrictedCashAndCashEquivalentsNoncurrent` actually being used?" ] }, { "cell_type": "code", "execution_count": 8, "id": "fa0c6a89-f4f6-44f1-9d1b-5a1c343e9323", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "15666" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum(all_bs_joinedbag.pre_num_df.tag == \"RestrictedCashAndCashEquivalentsNoncurrent\")" ] }, { "cell_type": "markdown", "id": "4dc5eb4f-6705-4ebb-b812-ba72f0c07627", "metadata": {}, "source": [ "As it is with LLMs, sometimes information is made up, or does not actually reflect actual official tags. None the less, LLMs can help to get a good understanding of the hierarchy of tags." ] }, { "cell_type": "markdown", "id": "3d4652a3-3746-4c7e-8524-ea65e8b24df6", "metadata": {}, "source": [ "Now, if you want to know for a certain report what tag it used to report a certain position, you can have a look at EDGAR at sec.gov itself: https://www.sec.gov/search-filings\n", "\n", "So let us have a look at the latest 10-Q report of Apple.\n", "\n", "In the overview for Apple (https://www.sec.gov/edgar/browse/?CIK=320193&owner=exclude), you can use the predefined filter `Annual & quarterly reports` to show only the 10-K and 10-Q filings. I also like to see the `Accession number`, so i marked that checkbox as well.\n", "\n", "\"Apple\n", "\n", "As of today (September 2024), the latest quaterly report was filed on 2nd August 2024 (accession number 0000320193-24-000081). So let us open its details (https://www.sec.gov/Archives/edgar/data/320193/000032019324000081/0000320193-24-000081-index.htm).\n", "\n", "\"Apple\n", "\n", "There are two paths to find the used tag for a certain position. Either you can use the `Interactive Data` or you can view the real report as rendered html. " ] }, { "cell_type": "markdown", "id": "f5191186-5a9e-4801-a188-064f237af8a7", "metadata": {}, "source": [ "Let's first have a look at how to use the `Interactive Data`. So, press on that button and navigate to the balance sheet:\n", "\n", "\"Interactive\n", "\n", "The interactive data directly present the most important tables of a report as a nicely rendered tables.\n", "\n", "For instance, let us find out what tag is used to report `Deferred revenue`. To do that, we simply click on that entry which opens a dialog box that describes the position. We can then open the details to see which tag is used to report that value:\n", "\n", "\"Deferred\n", "\n", "As we can see, the used tag to report that position is `ContractWithCustomerLiabilityCurrent`." ] }, { "cell_type": "markdown", "id": "19149efd-40a1-4085-84c3-29db80651cc7", "metadata": {}, "source": [ "Let's have a look at the second possible path. So, in the report overview instead of clicking on `Interactive Data` click on the first link in the `Document` column of the table. This opens the actual report.\n", "\n", "\"10-Q\n", "\n", "You can either scroll down till you find the balance sheet, or you can click on `Sections`, which opens the `Tagged Sections` side bar; click on `Financial Statements` and then `Condensed Consolidated Balance Sheets`.\n", "\n", "This scrolls down to the balance sheet. And here again, you can directly click on a `Fact` (a value), which opens a little dialog with detailed information about that position.\n", "\n", "\"Deferred\n" ] }, { "cell_type": "code", "execution_count": null, "id": "d05bde1a-3b88-41bc-be67-121f62bc563e", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.0" } }, "nbformat": 4, "nbformat_minor": 5 }