Example: Apple 2020¶
Quick shortcut¶
In [1]:
from openesef.edgar.loader import get_xbrl_df_by_ticker_year
from openesef.engines.tax_pres import get_current_fact_df
In [2]:
apple_2020 = get_xbrl_df_by_ticker_year(ticker="AAPL", year=2020, force_reload=False)
apple_2020_current_facts = get_current_fact_df(apple_2020.get("fact_df", None))
In [3]:
apple_2020_current_facts.loc[(apple_2020_current_facts.statement_type == "SOP") & (apple_2020_current_facts.fact_included == True),
["concept_qname", "label", "segment_axis_member", "value_mln", "order", "fact_index"]]
Out[3]:
concept_qname | label | segment_axis_member | value_mln | order | fact_index | |
---|---|---|---|---|---|---|
0 | us-gaap:RevenueFromContractWithCustomerExcludi... | Products | us-gaap:ProductMember | 220747.0 | 1.0 | 82 |
3 | us-gaap:RevenueFromContractWithCustomerExcludi... | Services | us-gaap:ServiceMember | 53768.0 | 1.0 | 85 |
6 | us-gaap:RevenueFromContractWithCustomerExcludi... | Net sales | None | 274515.0 | 1.0 | 88 |
9 | us-gaap:CostOfGoodsAndServicesSold | Products | us-gaap:ProductMember | 151286.0 | 2.0 | 91 |
12 | us-gaap:CostOfGoodsAndServicesSold | Services | us-gaap:ServiceMember | 18273.0 | 2.0 | 94 |
15 | us-gaap:CostOfGoodsAndServicesSold | Cost of sales | None | 169559.0 | 2.0 | 97 |
18 | us-gaap:GrossProfit | Gross margin | None | 104956.0 | 3.0 | 100 |
21 | us-gaap:ResearchAndDevelopmentExpense | Research and development | None | 18752.0 | 1.0 | 103 |
24 | us-gaap:SellingGeneralAndAdministrativeExpense | Selling, general and administrative | None | 19916.0 | 2.0 | 106 |
27 | us-gaap:OperatingExpenses | Operating Expenses | None | 38668.0 | 3.0 | 109 |
30 | us-gaap:OperatingIncomeLoss | Operating income | None | 66288.0 | 5.0 | 112 |
33 | us-gaap:NonoperatingIncomeExpense | Other income/(expense), net | None | 803.0 | 6.0 | 115 |
36 | us-gaap:IncomeLossFromContinuingOperationsBefo... | Income (Loss) from Continuing Operations befor... | None | 67091.0 | 7.0 | 118 |
39 | us-gaap:IncomeTaxExpenseBenefit | Provision for income taxes | None | 9680.0 | 8.0 | 121 |
42 | us-gaap:NetIncomeLoss | Net income | None | 57411.0 | 9.0 | 124 |
45 | us-gaap:EarningsPerShareBasic | Basic (in dollars per share) | None | NaN | 1.0 | 127 |
48 | us-gaap:EarningsPerShareDiluted | Diluted (in dollars per share) | None | NaN | 2.0 | 130 |
51 | us-gaap:WeightedAverageNumberOfSharesOutstandi... | Basic (in shares) | None | NaN | 1.0 | 133 |
54 | us-gaap:WeightedAverageNumberOfDilutedSharesOu... | Diluted (in shares) | None | NaN | 2.0 | 136 |
The above is the Statement of Operations from XBRL extraction; below is a screenshot of the same statement from the Form 10-K filing.

Below is the income statement R2.htm from the Form 10-K filing.
In [4]:
#https://www.sec.gov/Archives/edgar/data/320193/000032019320000096/R2.htm
import requests
from IPython.display import HTML
# Fetch the content
response = requests.get('https://www.sec.gov/Archives/edgar/data/320193/000032019320000096/R2.htm',
headers={"user-agent": "Your Name [email protected]"})
html_content = response.text
# Display the HTML content
HTML(html_content)
Out[4]:
CONSOLIDATED STATEMENTS OF OPERATIONS - USD ($) shares in Thousands, $ in Millions |
12 Months Ended | ||
---|---|---|---|
Sep. 26, 2020 |
Sep. 28, 2019 |
Sep. 29, 2018 |
|
Net sales | $ 274,515 | $ 260,174 | $ 265,595 |
Cost of sales | 169,559 | 161,782 | 163,756 |
Gross margin | 104,956 | 98,392 | 101,839 |
Operating expenses: | |||
Research and development | 18,752 | 16,217 | 14,236 |
Selling, general and administrative | 19,916 | 18,245 | 16,705 |
Total operating expenses | 38,668 | 34,462 | 30,941 |
Operating income | 66,288 | 63,930 | 70,898 |
Other income/(expense), net | 803 | 1,807 | 2,005 |
Income before provision for income taxes | 67,091 | 65,737 | 72,903 |
Provision for income taxes | 9,680 | 10,481 | 13,372 |
Net income | $ 57,411 | $ 55,256 | $ 59,531 |
Earnings per share: | |||
Basic (in dollars per share) | $ 3.31 | $ 2.99 | $ 3.00 |
Diluted (in dollars per share) | $ 3.28 | $ 2.97 | $ 2.98 |
Shares used in computing earnings per share: | |||
Basic (in shares) | 17,352,119 | 18,471,336 | 19,821,510 |
Diluted (in shares) | 17,528,214 | 18,595,651 | 20,000,435 |
Products | |||
Net sales | $ 220,747 | $ 213,883 | $ 225,847 |
Cost of sales | 151,286 | 144,996 | 148,164 |
Services | |||
Net sales | 53,768 | 46,291 | 39,748 |
Cost of sales | $ 18,273 | $ 16,786 | $ 15,592 |
Some detailed steps¶
First, lets load the logger:
In [5]:
import logging
from openesef.util.util_mylogger import setup_logger
logger = setup_logger("main", logging.CRITICAL, log_dir="/tmp/log/")
Now, lets load the xbrl filing, using Apple 2020 as an example:
In [6]:
from openesef.edgar.loader import load_xbrl_filing
xid, tax = load_xbrl_filing(ticker="AAPL", year=2020)
#xid, tax = load_xbrl_filing(filing_url="/Archives/edgar/data/320193/0000320193-20-000096.txt")
The xbrl filing is loaded and the taxonomy is created.
Now, lets print the XBRL instance (xid):
In [7]:
print(xid)
Namespaces: 10 Schema references: 1 Linkbase references: 0 Contexts: 318 Units: 9 Facts: 1388 Footnotes: 0 Filing Indicators: 0
And the taxonomy (tax):
In [8]:
print(tax)
Schemas: 14 Linkbases: 7 Role Types: 775 Arcrole Types: 0 Concepts: 18293 Item Types: 52 Tuple Types: 0 Simple Types: 0 Labels: 0 References: 0 Hierarchies: 158 Dimensional Relationship Sets: 158 Dimensions: 295 Hypercubes: 363
DEI stands for Document and Entity Information. For each XBRL report, there will be a section for DEI, and this class is to provide easy access to those commonly-defined DEI attributes.
Lets print the DEI:
In [9]:
for i, (key, value) in enumerate(xid.dei.items()):
print(f"{i}: {key}: {value}")
if i>7:
break
0: AmendmentFlag: false 1: DocumentFiscalYearFocus: 2020 2: DocumentFiscalPeriodFocus: FY 3: EntityCentralIndexKey: 0000320193 4: CurrentFiscalYearEndDate: --09-26 5: DocumentType: 10-K 6: DocumentAnnualReport: true 7: DocumentPeriodEndDate: 2020-09-26 8: DocumentTransitionReport: false
In [10]:
from openesef.engines.tax_pres import TaxonomyPresentation
t_pres = TaxonomyPresentation(tax)
In [11]:
print("\nConcept Labels in Statement of Operations:")
concepts_statement_of_operations = []
iprint = 0
# Get the list of concepts for the Statement of Operations directly
statement_concepts = t_pres.statement_concepts.get('CONSOLIDATEDSTATEMENTSOFOPERATIONS', [])
for concept in statement_concepts:
concepts_statement_of_operations.append(concept['concept_qname'])
print("-"*30)
print(f"Statement: {concept['statement_name']}")
print(f"Concept: {concept['concept_qname']}")
print(f"Label: {concept['label']}")
iprint += 1
if iprint > 3:
break
Concept Labels in Statement of Operations: ------------------------------ Statement: CONSOLIDATEDSTATEMENTSOFOPERATIONS Concept: us-gaap:StatementTable Label: Statement [Table] ------------------------------ Statement: CONSOLIDATEDSTATEMENTSOFOPERATIONS Concept: us-gaap:StatementTable Label: Statement [Table] ------------------------------ Statement: CONSOLIDATEDSTATEMENTSOFOPERATIONS Concept: srt:ProductOrServiceAxis Label: Product and Service [Axis] ------------------------------ Statement: CONSOLIDATEDSTATEMENTSOFOPERATIONS Concept: srt:ProductOrServiceAxis Label: Product and Service [Axis]
In [12]:
# Get the current year's main instance context
periods_dict = xid.identify_reporting_contexts()
#import pandas as pd
#print(pd.DataFrame.from_dict(periods_dict, orient='index'))
In [13]:
current_contexts = [ctx_id for ctx_id, ctx_info in periods_dict.items()
if ctx_info['relative_year'] == 0 and ctx_info.get('main_context')]
print(current_contexts)
['i747bec89b4e84f74ae3445db3509f609_I20200926', 'i223bd574caab4f739f73936be6065c72_D20190929-20200926', 'ic3ea678a3e394e00880d68882e8bdc02_I20200327', 'i5085fb79a9a14a9aae9b909beb32bce2_D20180930-20190928', 'i4920908218084be688c8fa96b8903033_D20171001-20180929']
In [14]:
print("\nFact Values:")
iprint=0
for key, fact in xid.xbrl.facts.items():
concept_qname = fact.qname if hasattr(fact, 'qname') else 'N/A' # Get the concept's QName
context = xid.xbrl.contexts[fact.context_ref]
period_info = periods_dict.get(fact.context_ref, {})
period_string = period_info.get('period_string', 'N/A')
if concept_qname in concepts_statement_of_operations and fact.context_ref in current_contexts:
print(f"{concept_qname:<90} Value: {fact.value:<15} Context: {period_string}")
iprint+=1
if iprint>7:
break
Fact Values:
In [15]:
def is_numeric(x):
try:
float(x)
return True
except (ValueError, TypeError):
return False
In [16]:
from openesef.engines.tax_pres import ins_facts
fact_df = ins_facts(xid, tax)
fact_df["val_mln"] = fact_df["value"].apply(lambda x: float(x)/1000000 if is_numeric(x) and float(x) > 1000000 else x)
fact_df.sort_values(by='fact_index', inplace=True)
fact_df = fact_df.loc[fact_df.fact_included ]
current_period_string = fact_df.period_string.value_counts().index[0]
current_facts = fact_df[fact_df.period_string == current_period_string].reset_index(drop=True)
In [17]:
current_facts.loc[(current_facts['statement_name'] == 'CONSOLIDATEDSTATEMENTSOFOPERATIONS') , ['fact_index', 'concept_name', 'label', "segment_axis", 'val_mln', 'period_end']].head(30)
#print(current_facts.loc[(current_facts['statement_name'] == 'CONSOLIDATEDSTATEMENTSOFOPERATIONS') , ["concept_qname", 'label', 'val_mln', "fact_included"]].to_markdown())
Out[17]:
fact_index | concept_name | label | segment_axis | val_mln | period_end | |
---|---|---|---|---|---|---|
0 | 82 | RevenueFromContractWithCustomerExcludingAssess... | Products | srt:ProductOrServiceAxis | 220747.0 | 2020-09-26 |
1 | 85 | RevenueFromContractWithCustomerExcludingAssess... | Services | srt:ProductOrServiceAxis | 53768.0 | 2020-09-26 |
2 | 88 | RevenueFromContractWithCustomerExcludingAssess... | Net sales | None | 274515.0 | 2020-09-26 |
3 | 91 | CostOfGoodsAndServicesSold | Products | srt:ProductOrServiceAxis | 151286.0 | 2020-09-26 |
4 | 94 | CostOfGoodsAndServicesSold | Services | srt:ProductOrServiceAxis | 18273.0 | 2020-09-26 |
5 | 97 | CostOfGoodsAndServicesSold | Cost of sales | None | 169559.0 | 2020-09-26 |
6 | 100 | GrossProfit | Gross margin | None | 104956.0 | 2020-09-26 |
7 | 103 | ResearchAndDevelopmentExpense | Research and development | None | 18752.0 | 2020-09-26 |
8 | 106 | SellingGeneralAndAdministrativeExpense | Selling, general and administrative | None | 19916.0 | 2020-09-26 |
9 | 109 | OperatingExpenses | Operating Expenses | None | 38668.0 | 2020-09-26 |
10 | 112 | OperatingIncomeLoss | Operating income | None | 66288.0 | 2020-09-26 |
11 | 115 | NonoperatingIncomeExpense | Other income/(expense), net | None | 803.0 | 2020-09-26 |
12 | 118 | IncomeLossFromContinuingOperationsBeforeIncome... | Income (Loss) from Continuing Operations befor... | None | 67091.0 | 2020-09-26 |
13 | 121 | IncomeTaxExpenseBenefit | Provision for income taxes | None | 9680.0 | 2020-09-26 |
16 | 124 | NetIncomeLoss | Net income | None | 57411.0 | 2020-09-26 |
18 | 127 | EarningsPerShareBasic | Basic (in dollars per share) | None | 3.31 | 2020-09-26 |
19 | 130 | EarningsPerShareDiluted | Diluted (in dollars per share) | None | 3.28 | 2020-09-26 |
20 | 133 | WeightedAverageNumberOfSharesOutstandingBasic | Basic (in shares) | None | 17352.119 | 2020-09-26 |
21 | 136 | WeightedAverageNumberOfDilutedSharesOutstanding | Diluted (in shares) | None | 17528.214 | 2020-09-26 |
In [ ]:
In [18]:
from openesef.engines.tax_pres import tax_calc_df
calc_df = tax_calc_df(tax)
calc_df.role_name.value_counts()
Out[18]:
role_name CONSOLIDATEDSTATEMENTSOFCASHFLOWS 31 CONSOLIDATEDBALANCESHEETS 26 IncomeTaxesSignificantComponentsofDeferredTaxAssetsandLiabilitiesDetails 11 CONSOLIDATEDSTATEMENTSOFOPERATIONS 10 CONSOLIDATEDSTATEMENTSOFCOMPREHENSIVEINCOME 9 IncomeTaxesProvisionforIncomeTaxesDetails 9 LeasesLeaseLiabilityMaturitiesDetails 8 LeasesLeaseLiabilityMaturitiesDetails_1 8 IncomeTaxesReconciliationoftheProvisionforIncomeTaxesDetails 7 FinancialInstrumentsCashCashEquivalentsandMarketableSecuritiesDetails 6 DebtFuturePrincipalPaymentsforTermDebtDetails 6 CommitmentsandContingenciesFuturePaymentsUnderUnconditionalPurchaseObligationsDetails 6 DebtSummaryofCashFlowsAssociatedwithCommercialPaperDetails 4 FinancialInstrumentsRestrictedCashDetails 3 ComprehensiveIncomeChangeinAOCIbyComponentDetails 3 ConsolidatedFinancialStatementDetailsOtherIncomeExpenseNetDetails 3 ConsolidatedFinancialStatementDetailsOtherNonCurrentLiabilitiesDetails 2 ConsolidatedFinancialStatementDetailsPropertyPlantandEquipmentNetDetails 2 SummaryofSignificantAccountingPoliciesComputationofBasicandDilutedEarningsPerShareDetails 2 Name: count, dtype: int64
In [19]:
calc_df["order"] = calc_df["order"].astype(float)
calc_df.loc[(calc_df.role_name.str.contains("CONSOLIDATEDSTATEMENTSOFCASHFLOWS")) & (calc_df.from_qname.str.contains("NetCashProvidedByUsedInOperatingActivities"))].sort_values(by=["order", "weight"], ascending=True)
Out[19]:
role | role_name | from_qname | to_qname | weight | order | |
---|---|---|---|---|---|---|
64 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:NetIncomeLoss | 1.0 | 1.0 |
65 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:DepreciationDepletionAndAmortization | 1.0 | 2.0 |
66 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:ShareBasedCompensation | 1.0 | 3.0 |
67 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:DeferredIncomeTaxExpenseBenefit | 1.0 | 4.0 |
68 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:OtherNoncashIncomeExpense | -1.0 | 5.0 |
69 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:IncreaseDecreaseInAccountsReceivable | -1.0 | 6.0 |
70 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:IncreaseDecreaseInInventories | -1.0 | 7.0 |
71 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:IncreaseDecreaseInOtherReceivables | -1.0 | 8.0 |
72 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:IncreaseDecreaseInOtherOperatingAssets | -1.0 | 9.0 |
73 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:IncreaseDecreaseInAccountsPayable | 1.0 | 10.0 |
74 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:IncreaseDecreaseInContractWithCustomer... | 1.0 | 11.0 |
75 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFCASHFLOWS | us-gaap:NetCashProvidedByUsedInOperatingActivi... | us-gaap:IncreaseDecreaseInOtherOperatingLiabil... | 1.0 | 12.0 |
In [ ]:
In [20]:
calc_df.loc[calc_df.role_name.str.contains("CONSOLIDATEDSTATEMENTSOFOPERATIONS")].sort_values(by=["from_qname", "order"], ascending=True)
Out[20]:
role | role_name | from_qname | to_qname | weight | order | |
---|---|---|---|---|---|---|
0 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:GrossProfit | us-gaap:RevenueFromContractWithCustomerExcludi... | 1.0 | 1.0 |
1 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:GrossProfit | us-gaap:CostOfGoodsAndServicesSold | -1.0 | 2.0 |
4 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:IncomeLossFromContinuingOperationsBefo... | us-gaap:OperatingIncomeLoss | 1.0 | 1.0 |
5 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:IncomeLossFromContinuingOperationsBefo... | us-gaap:NonoperatingIncomeExpense | 1.0 | 2.0 |
2 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:NetIncomeLoss | us-gaap:IncomeLossFromContinuingOperationsBefo... | 1.0 | 1.0 |
3 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:NetIncomeLoss | us-gaap:IncomeTaxExpenseBenefit | -1.0 | 2.0 |
6 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:OperatingExpenses | us-gaap:ResearchAndDevelopmentExpense | 1.0 | 1.0 |
7 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:OperatingExpenses | us-gaap:SellingGeneralAndAdministrativeExpense | 1.0 | 2.0 |
8 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:OperatingIncomeLoss | us-gaap:GrossProfit | 1.0 | 1.0 |
9 | http://www.apple.com/role/CONSOLIDATEDSTATEMEN... | CONSOLIDATEDSTATEMENTSOFOPERATIONS | us-gaap:OperatingIncomeLoss | us-gaap:OperatingExpenses | -1.0 | 2.0 |
In [21]:
this_calc = current_facts.loc[(current_facts['statement_name'] == 'CONSOLIDATEDSTATEMENTSOFOPERATIONS') , [ 'concept_name',"calc_children", "calc_parents", "calc_children_with_weights", "calc_parents_with_weights"]].head(30)
this_calc.iloc[6].to_dict()
Out[21]:
{'concept_name': 'GrossProfit', 'calc_children': ['us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax', 'us-gaap:CostOfGoodsAndServicesSold'], 'calc_parents': ['us-gaap:OperatingIncomeLoss'], 'calc_children_with_weights': {'us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax': 1.0, 'us-gaap:CostOfGoodsAndServicesSold': -1.0}, 'calc_parents_with_weights': {'us-gaap:OperatingIncomeLoss': 1.0}}